get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/126511/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 126511,
    "url": "http://patches.dpdk.org/api/patches/126511/?format=api",
    "web_url": "http://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/",
    "project": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20230425131516.3308612-5-vattunuru@marvell.com>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20230425131516.3308612-5-vattunuru@marvell.com",
    "date": "2023-04-25T13:15:16",
    "name": "[v2,4/4] app: add testgraph application",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "50a6ec9e4077b17b6a02c5d859c0138d1bbaf368",
    "submitter": {
        "id": 1277,
        "url": "http://patches.dpdk.org/api/people/1277/?format=api",
        "name": "Vamsi Krishna Attunuru",
        "email": "vattunuru@marvell.com"
    },
    "delegate": {
        "id": 1,
        "url": "http://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "http://patches.dpdk.org/project/dpdk/patch/20230425131516.3308612-5-vattunuru@marvell.com/mbox/",
    "series": [
        {
            "id": 27859,
            "url": "http://patches.dpdk.org/api/series/27859/?format=api",
            "web_url": "http://patches.dpdk.org/project/dpdk/list/?series=27859",
            "date": "2023-04-25T13:15:12",
            "name": "app: introduce testgraph application",
            "version": 2,
            "mbox": "http://patches.dpdk.org/series/27859/mbox/"
        }
    ],
    "comments": "http://patches.dpdk.org/api/patches/126511/comments/",
    "check": "fail",
    "checks": "http://patches.dpdk.org/api/patches/126511/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id B7F0D429EB;\n\tTue, 25 Apr 2023 15:16:12 +0200 (CEST)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id 3732B42D12;\n\tTue, 25 Apr 2023 15:15:55 +0200 (CEST)",
            "from mx0b-0016f401.pphosted.com (mx0b-0016f401.pphosted.com\n [67.231.156.173])\n by mails.dpdk.org (Postfix) with ESMTP id 0148342D12\n for <dev@dpdk.org>; Tue, 25 Apr 2023 15:15:52 +0200 (CEST)",
            "from pps.filterd (m0045851.ppops.net [127.0.0.1])\n by mx0b-0016f401.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id\n 33PCAm6K010073; Tue, 25 Apr 2023 06:15:52 -0700",
            "from dc5-exch01.marvell.com ([199.233.59.181])\n by mx0b-0016f401.pphosted.com (PPS) with ESMTPS id 3q4f3pa6bf-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-SHA384 bits=256 verify=NOT);\n Tue, 25 Apr 2023 06:15:51 -0700",
            "from DC5-EXCH01.marvell.com (10.69.176.38) by DC5-EXCH01.marvell.com\n (10.69.176.38) with Microsoft SMTP Server (TLS) id 15.0.1497.48;\n Tue, 25 Apr 2023 06:15:49 -0700",
            "from maili.marvell.com (10.69.176.80) by DC5-EXCH01.marvell.com\n (10.69.176.38) with Microsoft SMTP Server id 15.0.1497.48 via Frontend\n Transport; Tue, 25 Apr 2023 06:15:49 -0700",
            "from localhost.localdomain (unknown [10.28.36.156])\n by maili.marvell.com (Postfix) with ESMTP id BD6913F7057;\n Tue, 25 Apr 2023 06:15:46 -0700 (PDT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=marvell.com;\n h=from : to : cc :\n subject : date : message-id : in-reply-to : references : mime-version :\n content-transfer-encoding : content-type; s=pfpt0220;\n bh=J39trFeDpN4dIEbrXa4b7chli6twzcYemYZllrBy31A=;\n b=MIsOnvdEeRhkUs586SrxwRDBQRRRZ/yKkLXeD35XjSFj7+12bZK4cLOG56iPwSJgO776\n B54aCd2RidHM5G48FDi/xOMtkswEVqNP29T1lgpt/V9XEDVZw0AzS73gFjU6xdSzgqFt\n tTC/JxeTuttSH2k9K/99X3kQaEUZbwi1jgp5Dwg3JPL1LDWvybZXmxeu+mFMRHBYRB7Q\n zMnI3jmM3EPvKtPMaqB64dh0VE2vx/dYSAS+JOEh2azUVqA2gZ/1Su4qv1aLeRXrfuNV\n YeExm74uAA/SLWu5+hcZrEn/EsSE2ffc3tmzLMdWMckqQYXHVVjtiyPkZpu4ZP5JqWKg Og==",
        "From": "Vamsi Attunuru <vattunuru@marvell.com>",
        "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",
        "Date": "Tue, 25 Apr 2023 06:15:16 -0700",
        "Message-ID": "<20230425131516.3308612-5-vattunuru@marvell.com>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20230425131516.3308612-1-vattunuru@marvell.com>",
        "References": "<20230421060245.3136217-1-vattunuru@marvell.com>\n <20230425131516.3308612-1-vattunuru@marvell.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Content-Type": "text/plain",
        "X-Proofpoint-ORIG-GUID": "K1qK5yUKxS1lZurT7pGXsamxzT1aKWfz",
        "X-Proofpoint-GUID": "K1qK5yUKxS1lZurT7pGXsamxzT1aKWfz",
        "X-Proofpoint-Virus-Version": "vendor=baseguard\n engine=ICAP:2.0.254,Aquarius:18.0.942,Hydra:6.0.573,FMLib:17.11.170.22\n definitions=2023-04-25_05,2023-04-25_01,2023-02-09_01",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org"
    },
    "content": "Patch adds test-graph application to validate graph\nand node libraries.\n\nSigned-off-by: Vamsi Attunuru <vattunuru@marvell.com>\n---\n app/meson.build                |    1 +\n app/test-graph/cmdline.c       |  211 +++++\n app/test-graph/cmdline_graph.c |  294 +++++++\n app/test-graph/cmdline_graph.h |   19 +\n app/test-graph/meson.build     |   14 +\n app/test-graph/parameters.c    |  157 ++++\n app/test-graph/testgraph.c     | 1426 ++++++++++++++++++++++++++++++++\n app/test-graph/testgraph.h     |   91 ++\n doc/guides/tools/index.rst     |    1 +\n doc/guides/tools/testgraph.rst |  131 +++\n 10 files changed, 2345 insertions(+)",
    "diff": "diff --git a/app/meson.build b/app/meson.build\nindex 74d2420f67..6c7b24e604 100644\n--- a/app/meson.build\n+++ b/app/meson.build\n@@ -22,6 +22,7 @@ apps = [\n         'test-eventdev',\n         'test-fib',\n         'test-flow-perf',\n+        'test-graph',\n         'test-gpudev',\n         'test-mldev',\n         'test-pipeline',\ndiff --git a/app/test-graph/cmdline.c b/app/test-graph/cmdline.c\nnew file mode 100644\nindex 0000000000..d9474d827a\n--- /dev/null\n+++ b/app/test-graph/cmdline.c\n@@ -0,0 +1,211 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(C) 2023 Marvell International Ltd.\n+ */\n+\n+#include <stdlib.h>\n+\n+#include <cmdline.h>\n+#include <cmdline_parse.h>\n+#include <cmdline_parse_etheraddr.h>\n+#include <cmdline_parse_ipaddr.h>\n+#include <cmdline_parse_num.h>\n+#include <cmdline_parse_string.h>\n+#include <cmdline_rdline.h>\n+#include <cmdline_socket.h>\n+\n+#include \"cmdline_graph.h\"\n+#include \"testgraph.h\"\n+\n+static struct cmdline *testgraph_cl;\n+static cmdline_parse_ctx_t *main_ctx;\n+\n+/* *** Help command with introduction. *** */\n+struct cmd_help_brief_result {\n+\tcmdline_fixed_string_t help;\n+};\n+\n+static void\n+cmd_help_brief_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)\n+{\n+\tcmdline_printf(cl,\n+\t\t       \"\\n\"\n+\t\t       \"Help is available for the following sections:\\n\\n\"\n+\t\t       \"    help control                    : Start and stop graph walk.\\n\"\n+\t\t       \"    help display                    : Displaying port, stats and config \"\n+\t\t       \"information.\\n\"\n+\t\t       \"    help config                     : Configuration information.\\n\"\n+\t\t       \"    help all                        : All of the above sections.\\n\\n\");\n+}\n+\n+static cmdline_parse_token_string_t cmd_help_brief_help =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_help_brief_result, help, \"help\");\n+\n+static cmdline_parse_inst_t cmd_help_brief = {\n+\t.f = cmd_help_brief_parsed,\n+\t.data = NULL,\n+\t.help_str = \"help: Show help\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_help_brief_help,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/* *** Help command with help sections. *** */\n+struct cmd_help_long_result {\n+\tcmdline_fixed_string_t help;\n+\tcmdline_fixed_string_t section;\n+};\n+\n+static void\n+cmd_help_long_parsed(void *parsed_result, struct cmdline *cl, __rte_unused void *data)\n+{\n+\tint show_all = 0;\n+\tstruct cmd_help_long_result *res = parsed_result;\n+\n+\tif (!strcmp(res->section, \"all\"))\n+\t\tshow_all = 1;\n+\n+\tif (show_all || !strcmp(res->section, \"control\")) {\n+\n+\t\tcmdline_printf(cl, \"\\n\"\n+\t\t\t\t   \"Control forwarding:\\n\"\n+\t\t\t\t   \"-------------------\\n\\n\"\n+\n+\t\t\t\t   \"start graph_walk\\n\"\n+\t\t\t\t   \" Start graph_walk on worker threads.\\n\\n\"\n+\n+\t\t\t\t   \"stop graph_walk\\n\"\n+\t\t\t\t   \" Stop worker threads from running graph_walk.\\n\\n\"\n+\n+\t\t\t\t   \"quit\\n\"\n+\t\t\t\t   \"    Quit to prompt.\\n\\n\");\n+\t}\n+\n+\tif (show_all || !strcmp(res->section, \"display\")) {\n+\n+\t\tcmdline_printf(cl,\n+\t\t\t       \"\\n\"\n+\t\t\t       \"Display:\\n\"\n+\t\t\t       \"--------\\n\\n\"\n+\n+\t\t\t       \"show node_list\\n\"\n+\t\t\t       \" Display the list of supported nodes.\\n\\n\"\n+\n+\t\t\t       \"show graph_stats\\n\"\n+\t\t\t       \" Display the node statistics of graph cluster.\\n\\n\");\n+\t}\n+\n+\tif (show_all || !strcmp(res->section, \"config\")) {\n+\t\tcmdline_printf(cl, \"\\n\"\n+\t\t\t\t   \"Configuration:\\n\"\n+\t\t\t\t   \"--------------\\n\"\n+\t\t\t\t   \"set lcore_config (port_id0,rxq0,lcore_idX),...\"\n+\t\t\t\t   \".....,(port_idX,rxqX,lcoreidY)\\n\"\n+\t\t\t\t   \" Set lcore configuration.\\n\\n\"\n+\n+\t\t\t\t   \"create_graph (node0_name,node1_name,...,nodeX_name)\\n\"\n+\t\t\t\t   \" Create graph instances using the provided node details.\\n\\n\"\n+\n+\t\t\t\t   \"destroy_graph\\n\"\n+\t\t\t\t   \" Destroy the graph instances.\\n\\n\");\n+\t}\n+}\n+\n+static cmdline_parse_token_string_t cmd_help_long_help =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_help_long_result, help, \"help\");\n+\n+static cmdline_parse_token_string_t cmd_help_long_section = TOKEN_STRING_INITIALIZER(\n+\tstruct cmd_help_long_result, section, \"all#control#display#config\");\n+\n+static cmdline_parse_inst_t cmd_help_long = {\n+\t.f = cmd_help_long_parsed,\n+\t.data = NULL,\n+\t.help_str = \"help all|control|display|config: \"\n+\t\t    \"Show help\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_help_long_help,\n+\t\t\t(void *)&cmd_help_long_section,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/* *** QUIT *** */\n+struct cmd_quit_result {\n+\tcmdline_fixed_string_t quit;\n+};\n+\n+static void\n+cmd_quit_parsed(__rte_unused void *parsed_result, struct cmdline *cl, __rte_unused void *data)\n+{\n+\tcmdline_quit(cl);\n+}\n+\n+static cmdline_parse_token_string_t cmd_quit_quit =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_quit_result, quit, \"quit\");\n+\n+static cmdline_parse_inst_t cmd_quit = {\n+\t.f = cmd_quit_parsed,\n+\t.data = NULL,\n+\t.help_str = \"quit: Exit application\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_quit_quit,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/* list of instructions */\n+static cmdline_parse_ctx_t builtin_ctx[] = {\n+\t(cmdline_parse_inst_t *)&cmd_help_brief,\n+\t(cmdline_parse_inst_t *)&cmd_help_long,\n+\t(cmdline_parse_inst_t *)&cmd_quit,\n+\t(cmdline_parse_inst_t *)&cmd_show_node_list,\n+\t(cmdline_parse_inst_t *)&cmd_set_lcore_config,\n+\t(cmdline_parse_inst_t *)&cmd_create_graph,\n+\t(cmdline_parse_inst_t *)&cmd_destroy_graph,\n+\t(cmdline_parse_inst_t *)&cmd_start_graph_walk,\n+\t(cmdline_parse_inst_t *)&cmd_stop_graph_walk,\n+\t(cmdline_parse_inst_t *)&cmd_show_graph_stats,\n+\tNULL,\n+};\n+\n+int\n+init_cmdline(void)\n+{\n+\tunsigned int count;\n+\tunsigned int i;\n+\n+\tcount = 0;\n+\tfor (i = 0; builtin_ctx[i] != NULL; i++)\n+\t\tcount++;\n+\n+\t/* cmdline expects a NULL terminated array */\n+\tmain_ctx = calloc(count + 1, sizeof(main_ctx[0]));\n+\tif (main_ctx == NULL)\n+\t\treturn -1;\n+\n+\tcount = 0;\n+\tfor (i = 0; builtin_ctx[i] != NULL; i++, count++)\n+\t\tmain_ctx[count] = builtin_ctx[i];\n+\n+\treturn 0;\n+}\n+\n+void\n+prompt_exit(void)\n+{\n+\tcmdline_quit(testgraph_cl);\n+}\n+\n+/* prompt function, called from main on MAIN lcore */\n+void\n+prompt(void)\n+{\n+\ttestgraph_cl = cmdline_stdin_new(main_ctx, \"testgraph> \");\n+\tif (testgraph_cl == NULL) {\n+\t\tfprintf(stderr, \"Failed to create stdin based cmdline context\\n\");\n+\t\treturn;\n+\t}\n+\n+\tcmdline_interact(testgraph_cl);\n+\tcmdline_stdin_exit(testgraph_cl);\n+}\ndiff --git a/app/test-graph/cmdline_graph.c b/app/test-graph/cmdline_graph.c\nnew file mode 100644\nindex 0000000000..d66149a224\n--- /dev/null\n+++ b/app/test-graph/cmdline_graph.c\n@@ -0,0 +1,294 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(C) 2023 Marvell International Ltd.\n+ */\n+\n+#include <stdlib.h>\n+#include <string.h>\n+\n+#include <cmdline_parse.h>\n+#include <cmdline_parse_num.h>\n+#include <cmdline_parse_string.h>\n+\n+#include \"cmdline_graph.h\"\n+#include \"testgraph.h\"\n+\n+/* *** Show supported node details *** */\n+struct cmd_show_node_list_result {\n+\tcmdline_fixed_string_t show;\n+\tcmdline_fixed_string_t node_list;\n+};\n+\n+static cmdline_parse_token_string_t cmd_show_node_list_show =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, show, \"show\");\n+static cmdline_parse_token_string_t cmd_show_node_list_node_list =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_show_node_list_result, node_list, \"node_list\");\n+\n+static void\n+cmd_show_node_list_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t  __rte_unused void *data)\n+{\n+\trte_node_t node_cnt = rte_node_max_count();\n+\trte_node_t id;\n+\n+\tprintf(\"\\n**** Supported Graph Nodes ****\\n\");\n+\tfor (id = 0; id < node_cnt; id++)\n+\t\tprintf(\"%s\\n\", rte_node_id_to_name(id));\n+\n+\tprintf(\"********************************\\n\");\n+}\n+\n+cmdline_parse_inst_t cmd_show_node_list = {\n+\t.f = cmd_show_node_list_parsed,\n+\t.data = NULL,\n+\t.help_str = \"show node_list\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_show_node_list_show,\n+\t\t\t(void *)&cmd_show_node_list_node_list,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/* *** Set lcore config *** */\n+struct cmd_set_lcore_config_result {\n+\tcmdline_fixed_string_t set;\n+\tcmdline_fixed_string_t lcore_config;\n+\tcmdline_multi_string_t token_string;\n+};\n+\n+static cmdline_parse_token_string_t cmd_set_lcore_config_set =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, set, \"set\");\n+static cmdline_parse_token_string_t cmd_set_lcore_config_lcore_config =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_set_lcore_config_result, lcore_config, \"lcore_config\");\n+static cmdline_parse_token_string_t cmd_set_lcore_config_token_string = TOKEN_STRING_INITIALIZER(\n+\tstruct cmd_set_lcore_config_result, token_string, TOKEN_STRING_MULTI);\n+\n+static void\n+cmd_set_lcore_config_parsed(void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t    __rte_unused void *data)\n+{\n+\tstruct cmd_set_lcore_config_result *res = parsed_result;\n+\tconst char *t_str = res->token_string;\n+\tint ret;\n+\n+\t/* Parse string */\n+\tret = parse_config(t_str);\n+\tif (ret) {\n+\t\tprintf(\" lcore_config string parse error\\n\");\n+\t\treturn;\n+\t}\n+\n+\tvalidate_config();\n+}\n+\n+cmdline_parse_inst_t cmd_set_lcore_config = {\n+\t.f = cmd_set_lcore_config_parsed,\n+\t.data = NULL,\n+\t.help_str = \"set lcore_config \"\n+\t\t    \"(port,queue,lcore),[(port,queue,lcore) ... (port,queue,lcore)]\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_set_lcore_config_set,\n+\t\t\t(void *)&cmd_set_lcore_config_lcore_config,\n+\t\t\t(void *)&cmd_set_lcore_config_token_string,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/* *** Create graph *** */\n+struct cmd_create_graph_result {\n+\tcmdline_fixed_string_t create_graph;\n+\tcmdline_multi_string_t token_string;\n+};\n+\n+static cmdline_parse_token_string_t cmd_create_graph_create_graph =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, create_graph, \"create_graph\");\n+static cmdline_parse_token_string_t cmd_create_graph_token_string =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_create_graph_result, token_string, TOKEN_STRING_MULTI);\n+\n+static void\n+cmd_create_graph_parsed(void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t__rte_unused void *data)\n+{\n+\tstruct cmd_create_graph_result *res = parsed_result;\n+\tconst char *t_str = res->token_string;\n+\tuint64_t valid_nodes = 0;\n+\tint ret;\n+\n+\tret = parse_node_patterns(t_str);\n+\tif (ret) {\n+\t\tprintf(\"parse_node_patterns failed\\n\");\n+\t\tcleanup_node_pattern();\n+\t\treturn;\n+\t}\n+\n+\tret = validate_node_names(&valid_nodes);\n+\tif (ret) {\n+\t\tprintf(\"validate_node_names() failed\\n\");\n+\t\tcleanup_node_pattern();\n+\t\treturn;\n+\t}\n+\n+\tnb_conf = ethdev_ports_setup();\n+\n+\tethdev_rxq_configure();\n+\tethdev_txq_configure();\n+\n+\tret = configure_graph_nodes(valid_nodes);\n+\tif (ret)\n+\t\trte_exit(EXIT_FAILURE, \"configure_graph_nodes: err=%d\\n\", ret);\n+\n+\tret = create_graph(valid_nodes);\n+\tif (ret)\n+\t\trte_exit(EXIT_FAILURE, \"create_graph: err=%d\\n\", ret);\n+\n+\tstats = create_graph_cluster_stats();\n+\tif (stats == NULL)\n+\t\trte_exit(EXIT_FAILURE, \"create_graph_cluster_stats() failed\\n\");\n+\n+\tcheck_all_ports_link_status(enabled_port_mask);\n+\tstart_eth_ports();\n+}\n+\n+cmdline_parse_inst_t cmd_create_graph = {\n+\t.f = cmd_create_graph_parsed,\n+\t.data = NULL,\n+\t.help_str = \"create_graph \"\n+\t\t    \"[node_name0,node_name1,node_name2 ... node_nameX]\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_create_graph_create_graph,\n+\t\t\t(void *)&cmd_create_graph_token_string,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/**** Destroy graph ****/\n+struct cmd_destroy_graph_result {\n+\tcmdline_fixed_string_t destroy_graph;\n+};\n+\n+static cmdline_parse_token_string_t cmd_destroy_graph_destroy_graph =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_destroy_graph_result, destroy_graph, \"destroy_graph\");\n+\n+static void\n+cmd_destroy_graph_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t __rte_unused void *data)\n+{\n+\tuint32_t lcore_id;\n+\n+\trun_graph_walk = false;\n+\tgraph_walk_quit = true;\n+\n+\tRTE_LCORE_FOREACH_WORKER(lcore_id)\n+\t\trte_eal_wait_lcore(lcore_id);\n+\n+\tdestroy_graph();\n+\tstop_eth_ports();\n+}\n+\n+cmdline_parse_inst_t cmd_destroy_graph = {\n+\t.f = cmd_destroy_graph_parsed,\n+\t.data = NULL,\n+\t.help_str = \"destroy_graph\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_destroy_graph_destroy_graph,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/**** Start graph_walk ****/\n+struct cmd_start_graph_walk_result {\n+\tcmdline_fixed_string_t start;\n+\tcmdline_fixed_string_t graph_walk;\n+};\n+\n+static cmdline_parse_token_string_t cmd_start_graph_walk_start =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, start, \"start\");\n+static cmdline_parse_token_string_t cmd_start_graph_walk_graph_walk =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_start_graph_walk_result, graph_walk, \"graph_walk\");\n+\n+static void\n+cmd_start_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t    __rte_unused void *data)\n+{\n+\tstatic bool launch_graph_walk;\n+\n+\tif (!launch_graph_walk) {\n+\t\trte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);\n+\t\tlaunch_graph_walk = true;\n+\t}\n+\n+\trun_graph_walk = true;\n+}\n+\n+cmdline_parse_inst_t cmd_start_graph_walk = {\n+\t.f = cmd_start_graph_walk_parsed,\n+\t.data = NULL,\n+\t.help_str = \"start graph_walk\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_start_graph_walk_start,\n+\t\t\t(void *)&cmd_start_graph_walk_graph_walk,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/**** Stop graph_walk ****/\n+struct cmd_stop_graph_walk_result {\n+\tcmdline_fixed_string_t stop;\n+\tcmdline_fixed_string_t graph_walk;\n+};\n+\n+static cmdline_parse_token_string_t cmd_stop_graph_walk_stop =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, stop, \"stop\");\n+static cmdline_parse_token_string_t cmd_stop_graph_walk_graph_walk =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_stop_graph_walk_result, graph_walk, \"graph_walk\");\n+\n+static void\n+cmd_stop_graph_walk_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t   __rte_unused void *data)\n+{\n+\trun_graph_walk = false;\n+}\n+\n+cmdline_parse_inst_t cmd_stop_graph_walk = {\n+\t.f = cmd_stop_graph_walk_parsed,\n+\t.data = NULL,\n+\t.help_str = \"stop graph_walk\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_stop_graph_walk_stop,\n+\t\t\t(void *)&cmd_stop_graph_walk_graph_walk,\n+\t\t\tNULL,\n+\t\t},\n+};\n+\n+/**** Show graph_stats ****/\n+struct cmd_show_graph_stats_result {\n+\tcmdline_fixed_string_t show;\n+\tcmdline_fixed_string_t graph_stats;\n+};\n+\n+static cmdline_parse_token_string_t cmd_show_graph_stats_show =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, show, \"show\");\n+static cmdline_parse_token_string_t cmd_show_graph_stats_graph_stats =\n+\tTOKEN_STRING_INITIALIZER(struct cmd_show_graph_stats_result, graph_stats, \"graph_stats\");\n+\n+static void\n+cmd_show_graph_stats_parsed(__rte_unused void *parsed_result, __rte_unused struct cmdline *cl,\n+\t\t\t    __rte_unused void *data)\n+{\n+\tif (rte_graph_has_stats_feature()) {\n+\t\tif (stats)\n+\t\t\trte_graph_cluster_stats_get(stats, 0);\n+\t} else {\n+\t\tprintf(\" graph stats feature not enabled in rte_config.\\n\");\n+\t}\n+}\n+\n+cmdline_parse_inst_t cmd_show_graph_stats = {\n+\t.f = cmd_show_graph_stats_parsed,\n+\t.data = NULL,\n+\t.help_str = \"show graph_stats\",\n+\t.tokens = {\n+\t\t\t(void *)&cmd_show_graph_stats_show,\n+\t\t\t(void *)&cmd_show_graph_stats_graph_stats,\n+\t\t\tNULL,\n+\t\t},\n+};\ndiff --git a/app/test-graph/cmdline_graph.h b/app/test-graph/cmdline_graph.h\nnew file mode 100644\nindex 0000000000..2846ff5425\n--- /dev/null\n+++ b/app/test-graph/cmdline_graph.h\n@@ -0,0 +1,19 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(C) 2023 Marvell International Ltd.\n+ */\n+\n+#ifndef _CMDLINE_GRAPH_H_\n+#define _CMDLINE_GRAPH_H_\n+\n+extern cmdline_parse_inst_t cmd_show_node_list;\n+extern cmdline_parse_inst_t cmd_set_lcore_config;\n+\n+extern cmdline_parse_inst_t cmd_create_graph;\n+extern cmdline_parse_inst_t cmd_destroy_graph;\n+\n+extern cmdline_parse_inst_t cmd_start_graph_walk;\n+extern cmdline_parse_inst_t cmd_stop_graph_walk;\n+\n+extern cmdline_parse_inst_t cmd_show_graph_stats;\n+\n+#endif /* _CMDLINE_GRAPH_H_ */\ndiff --git a/app/test-graph/meson.build b/app/test-graph/meson.build\nnew file mode 100644\nindex 0000000000..d93802a975\n--- /dev/null\n+++ b/app/test-graph/meson.build\n@@ -0,0 +1,14 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(C) 2023 Marvell International Ltd.\n+\n+# override default name to drop the hyphen\n+name = 'test-graph'\n+cflags += '-Wno-deprecated-declarations'\n+sources = files(\n+        'cmdline.c',\n+        'cmdline_graph.c',\n+        'parameters.c',\n+        'testgraph.c',\n+)\n+\n+deps += ['ethdev', 'cmdline', 'graph', 'node', 'eal']\ndiff --git a/app/test-graph/parameters.c b/app/test-graph/parameters.c\nnew file mode 100644\nindex 0000000000..b990ca4a1c\n--- /dev/null\n+++ b/app/test-graph/parameters.c\n@@ -0,0 +1,157 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(C) 2023 Marvell International Ltd.\n+ */\n+\n+#include <getopt.h>\n+#include <stdlib.h>\n+\n+#include \"testgraph.h\"\n+\n+static const char short_options[] = \"p:\" /* portmask */\n+\t\t\t\t    \"P\"\t /* promiscuous */\n+\t\t\t\t    \"i\"\t /* interactive */\n+\t;\n+\n+#define CMD_LINE_OPT_CONFIG\t   \"config\"\n+#define CMD_LINE_OPT_NODE_PATTERN  \"node-pattern\"\n+#define CMD_LINE_OPT_INTERACTIVE   \"interactive\"\n+#define CMD_LINE_OPT_NO_NUMA\t   \"no-numa\"\n+#define CMD_LINE_OPT_PER_PORT_POOL \"per-port-pool\"\n+enum {\n+\t/* Long options mapped to a short option */\n+\n+\t/* First long only option value must be >= 256, so that we won't\n+\t * conflict with short options\n+\t */\n+\tCMD_LINE_OPT_MIN_NUM = 256,\n+\tCMD_LINE_OPT_CONFIG_NUM,\n+\tCMD_LINE_OPT_NODE_PATTERN_NUM,\n+\tCMD_LINE_OPT_INTERACTIVE_NUM,\n+\tCMD_LINE_OPT_NO_NUMA_NUM,\n+\tCMD_LINE_OPT_PARSE_PER_PORT_POOL,\n+};\n+\n+static const struct option lgopts[] = {\n+\t{CMD_LINE_OPT_CONFIG, 1, 0, CMD_LINE_OPT_CONFIG_NUM},\n+\t{CMD_LINE_OPT_NODE_PATTERN, 1, 0, CMD_LINE_OPT_NODE_PATTERN_NUM},\n+\t{CMD_LINE_OPT_INTERACTIVE, 0, 0, CMD_LINE_OPT_INTERACTIVE_NUM},\n+\t{CMD_LINE_OPT_NO_NUMA, 0, 0, CMD_LINE_OPT_NO_NUMA_NUM},\n+\t{CMD_LINE_OPT_PER_PORT_POOL, 0, 0, CMD_LINE_OPT_PARSE_PER_PORT_POOL},\n+\t{NULL, 0, 0, 0},\n+};\n+\n+/* Display usage */\n+static void\n+print_usage(const char *prgname)\n+{\n+\tfprintf(stderr,\n+\t\t\"%s [EAL options] --\"\n+\t\t\" -p PORTMASK\"\n+\t\t\" [-P]\"\n+\t\t\" [-i]\"\n+\t\t\" --config (port,queue,lcore)[,(port,queue,lcore)]\"\n+\t\t\" --node-pattern (node_name0,node_name1[,node_nameX)]\"\n+\t\t\" [--no-numa]\"\n+\t\t\" [--per-port-pool]\"\n+\t\t\" [--interactive]\"\n+\n+\t\t\"  -p PORTMASK: Hexadecimal bitmask of ports to configure\\n\"\n+\t\t\"  -P : Enable promiscuous mode\\n\"\n+\t\t\"  -i : Enter interactive mode\\n\"\n+\t\t\"  --config (port,queue,lcore): Rx queue configuration\\n\"\n+\t\t\"  --node-pattern (node_names): node patterns to create graph\\n\"\n+\t\t\"  --no-numa: Disable numa awareness\\n\"\n+\t\t\"  --per-port-pool: Use separate buffer pool per port\\n\"\n+\t\t\"  --interactive: Enter interactive mode\\n\",\n+\t\tprgname);\n+}\n+\n+static int\n+parse_portmask(const char *portmask)\n+{\n+\tchar *end = NULL;\n+\tunsigned long pm;\n+\n+\t/* Parse hexadecimal string */\n+\tpm = strtoul(portmask, &end, 16);\n+\tif ((portmask[0] == '\\0') || (end == NULL) || (*end != '\\0'))\n+\t\treturn 0;\n+\n+\treturn pm;\n+}\n+\n+/* Parse the argument given in the command line of the application */\n+int\n+parse_cmdline_args(int argc, char **argv)\n+{\n+\tchar *prgname = argv[0];\n+\tint option_index;\n+\tchar **argvopt;\n+\tint opt, ret;\n+\n+\targvopt = argv;\n+\n+\t/* Error or normal output strings. */\n+\twhile ((opt = getopt_long(argc, argvopt, short_options, lgopts, &option_index)) != EOF) {\n+\n+\t\tswitch (opt) {\n+\t\t/* Portmask */\n+\t\tcase 'p':\n+\t\t\tenabled_port_mask = parse_portmask(optarg);\n+\t\t\tif (enabled_port_mask == 0) {\n+\t\t\t\tfprintf(stderr, \"Invalid portmask\\n\");\n+\t\t\t\tprint_usage(prgname);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase 'P':\n+\t\t\tpromiscuous_on = 1;\n+\t\t\tbreak;\n+\n+\t\t/* Long options */\n+\t\tcase CMD_LINE_OPT_CONFIG_NUM:\n+\t\t\tret = parse_config(optarg);\n+\t\t\tif (ret) {\n+\t\t\t\tfprintf(stderr, \"Invalid config\\n\");\n+\t\t\t\tprint_usage(prgname);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase CMD_LINE_OPT_NODE_PATTERN_NUM:\n+\t\t\tret = parse_node_patterns(optarg);\n+\t\t\tif (ret) {\n+\t\t\t\tfprintf(stderr, \"Invalid node_patterns\\n\");\n+\t\t\t\tprint_usage(prgname);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t\tbreak;\n+\n+\t\tcase CMD_LINE_OPT_INTERACTIVE_NUM:\n+\t\tcase 'i':\n+\t\t\tprintf(\"Interactive-mode selected\\n\");\n+\t\t\tinteractive = 1;\n+\t\t\tbreak;\n+\n+\t\tcase CMD_LINE_OPT_NO_NUMA_NUM:\n+\t\t\tnuma_on = 0;\n+\t\t\tbreak;\n+\n+\t\tcase CMD_LINE_OPT_PARSE_PER_PORT_POOL:\n+\t\t\tprintf(\"Per port buffer pool is enabled\\n\");\n+\t\t\tper_port_pool = 1;\n+\t\t\tbreak;\n+\n+\t\tdefault:\n+\t\t\tprint_usage(prgname);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\tif (optind >= 0)\n+\t\targv[optind - 1] = prgname;\n+\tret = optind - 1;\n+\toptind = 1; /* Reset getopt lib */\n+\n+\treturn ret;\n+}\ndiff --git a/app/test-graph/testgraph.c b/app/test-graph/testgraph.c\nnew file mode 100644\nindex 0000000000..aff921acf2\n--- /dev/null\n+++ b/app/test-graph/testgraph.c\n@@ -0,0 +1,1426 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(C) 2023 Marvell International Ltd.\n+ */\n+\n+#include <fnmatch.h>\n+#include <signal.h>\n+#include <stdlib.h>\n+\n+#include <rte_bus.h>\n+#include <rte_byteorder.h>\n+#include <rte_common.h>\n+#include <rte_dev.h>\n+#include <rte_eal.h>\n+#include <rte_ethdev.h>\n+#include <rte_ether.h>\n+#include <rte_graph_worker.h>\n+#include <rte_launch.h>\n+#include <rte_lcore.h>\n+#include <rte_log.h>\n+#include <rte_malloc.h>\n+#include <rte_mempool.h>\n+#include <rte_per_lcore.h>\n+#include <ethdev_rx_priv.h>\n+#include <ethdev_tx_priv.h>\n+#include <punt_kernel_priv.h>\n+#include <kernel_recv_priv.h>\n+\n+#include \"testgraph.h\"\n+\n+/* Log type */\n+#define RTE_LOGTYPE_TEST_GRAPH RTE_LOGTYPE_USER1\n+\n+/*\n+ * Configurable number of RX/TX ring descriptors\n+ */\n+#define RX_DESC_DEFAULT 1024\n+#define TX_DESC_DEFAULT 1024\n+\n+#define MAX_TX_QUEUE_PER_PORT RTE_MAX_ETHPORTS\n+#define MAX_RX_QUEUE_PER_PORT 128\n+\n+#define MAX_RX_QUEUE_PER_LCORE 16\n+\n+#define NB_SOCKETS 8\n+\n+/* Static global variables used within this file. */\n+uint16_t nb_rxd = RX_DESC_DEFAULT;\n+uint16_t nb_txd = TX_DESC_DEFAULT;\n+\n+static volatile bool force_quit;\n+volatile bool graph_walk_quit;\n+volatile bool run_graph_walk = true;\n+\n+char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE] = {0};\n+const char **node_patterns;\n+uint8_t num_patterns;\n+\n+uint8_t interactive; /**< interactive mode is off by default */\n+int promiscuous_on; /**< Ports set in promiscuous mode off by default. */\n+int numa_on = 1;   /**< NUMA is enabled by default. */\n+int per_port_pool; /**< Use separate buffer pools per port, disabled by default */\n+int testgraph_logtype; /**< Log type for testgraph logs */\n+\n+struct rte_graph_cluster_stats *stats;\n+\n+uint32_t enabled_port_mask; /**< Mask of enabled ports */\n+\n+uint32_t nb_conf;\n+\n+struct lcore_rx_queue {\n+\tuint16_t port_id;\n+\tuint8_t queue_id;\n+\tchar node_name[RTE_NODE_NAMESIZE];\n+};\n+\n+/* Lcore conf */\n+struct lcore_conf {\n+\tuint16_t n_rx_queue;\n+\tstruct lcore_rx_queue rx_queue_list[MAX_RX_QUEUE_PER_LCORE];\n+\tchar punt_kernel_node_name[RTE_NODE_NAMESIZE];\n+\tchar kernel_recv_node_name[RTE_NODE_NAMESIZE];\n+\n+\tstruct rte_graph *graph;\n+\tchar name[RTE_GRAPH_NAMESIZE];\n+\trte_graph_t graph_id;\n+} __rte_cache_aligned;\n+\n+static struct lcore_conf lcore_conf[RTE_MAX_LCORE];\n+\n+struct lcore_params lcore_params_array[MAX_LCORE_PARAMS];\n+static struct lcore_params lcore_params_array_default[] = {\n+\t{0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {0, 1, 3}, {1, 1, 3},\n+\t{2, 1, 3}, {0, 2, 4}, {1, 2, 4}, {2, 2, 4},\n+};\n+\n+struct lcore_params *lcore_params = lcore_params_array_default;\n+uint16_t nb_lcore_params = RTE_DIM(lcore_params_array_default);\n+\n+static struct rte_eth_conf port_conf = {\n+\t.rxmode = {\n+\t\t\t.mq_mode = RTE_ETH_MQ_RX_RSS,\n+\t\t},\n+\t.rx_adv_conf = {\n+\t\t\t.rss_conf = {\n+\t\t\t\t\t.rss_key = NULL,\n+\t\t\t\t\t.rss_hf = RTE_ETH_RSS_IP,\n+\t\t\t\t},\n+\t\t},\n+\t.txmode = {\n+\t\t\t.mq_mode = RTE_ETH_MQ_TX_NONE,\n+\t\t},\n+};\n+\n+static const struct node_list test_node_list[] = {{{\"ethdev_rx\", \"ethdev_tx\"}, 0, 2},\n+\t\t\t\t\t\t{{\"ethdev_rx\", \"punt_kernel\"}, 0, 2},\n+\t\t\t\t\t\t{{\"kernel_recv\", \"ethdev_tx\"}, 0, 2} };\n+\n+static const struct node_list supported_nodes[] = {{{\"ethdev_rx\"}, TEST_GRAPH_ETHDEV_RX_NODE, 1},\n+\t\t\t\t\t\t{{\"ethdev_tx\"}, TEST_GRAPH_ETHDEV_TX_NODE, 1},\n+\t\t\t\t\t\t{{\"punt_kernel\"}, TEST_GRAPH_PUNT_KERNEL_NODE, 1},\n+\t\t\t\t\t\t{{\"kernel_recv\"}, TEST_GRAPH_KERNEL_RECV_NODE, 1},\n+\t\t\t\t\t\t{{\"ip4_lookup\"}, TEST_GRAPH_IP4_LOOKUP_NODE, 1},\n+\t\t\t\t\t\t{{\"ip4_rewrite\"}, TEST_GRAPH_IP4_REWRITE_NODE, 1},\n+\t\t\t\t\t\t{{\"pkt_cls\"}, TEST_GRAPH_PKT_CLS_NODE, 1},\n+\t\t\t\t\t\t{{\"pkt_drop\"}, TEST_GRAPH_PKT_DROP_NODE, 1},\n+\t\t\t\t\t\t{{\"NULL\"}, TEST_GRAPH_NULL_NODE, 1} };\n+\n+static struct rte_mempool *pktmbuf_pool[RTE_MAX_ETHPORTS][NB_SOCKETS];\n+\n+struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];\n+\n+static int\n+check_lcore_params(void)\n+{\n+\tuint8_t queue, lcore;\n+\tint socketid;\n+\tuint16_t i;\n+\n+\tfor (i = 0; i < nb_lcore_params; ++i) {\n+\t\tqueue = lcore_params[i].queue_id;\n+\t\tif (queue >= MAX_RX_QUEUE_PER_PORT) {\n+\t\t\tprintf(\"Invalid queue number: %hhu\\n\", queue);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tlcore = lcore_params[i].lcore_id;\n+\t\tif (!rte_lcore_is_enabled(lcore)) {\n+\t\t\tprintf(\"Error: lcore %hhu is not enabled in lcore mask\\n\", lcore);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tif (lcore == rte_get_main_lcore()) {\n+\t\t\tprintf(\"Error: lcore %u is main lcore\\n\", lcore);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tsocketid = rte_lcore_to_socket_id(lcore);\n+\t\tif ((socketid != 0) && (numa_on == 0)) {\n+\t\t\tprintf(\"Warning: lcore %hhu is on socket %d with numa off\\n\", lcore,\n+\t\t\t       socketid);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+check_port_config(void)\n+{\n+\tuint16_t portid;\n+\tuint16_t i;\n+\n+\tfor (i = 0; i < nb_lcore_params; ++i) {\n+\t\tportid = lcore_params[i].port_id;\n+\t\tif ((enabled_port_mask & (1 << portid)) == 0) {\n+\t\t\tprintf(\"Port %u is not enabled in port mask\\n\", portid);\n+\t\t\treturn -1;\n+\t\t}\n+\t\tif (!rte_eth_dev_is_valid_port(portid)) {\n+\t\t\tprintf(\"Port %u is not present on the board\\n\", portid);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static uint8_t\n+get_port_n_rx_queues(const uint16_t port)\n+{\n+\tint queue = -1;\n+\tuint16_t i;\n+\n+\tfor (i = 0; i < nb_lcore_params; ++i) {\n+\t\tif (lcore_params[i].port_id == port) {\n+\t\t\tif (lcore_params[i].queue_id == queue + 1)\n+\t\t\t\tqueue = lcore_params[i].queue_id;\n+\t\t\telse\n+\t\t\t\trte_exit(EXIT_FAILURE, \"Queue ids of the port %d must be\"\n+\t\t\t\t\t \" in sequence and must start with 0\\n\",\n+\t\t\t\t\t lcore_params[i].port_id);\n+\t\t}\n+\t}\n+\n+\treturn (uint8_t)(++queue);\n+}\n+\n+static int\n+init_lcore_rx_queues(void)\n+{\n+\tuint16_t i, nb_rx_queue;\n+\tuint8_t lcore;\n+\n+\tfor (i = 0; i < nb_lcore_params; ++i) {\n+\t\tlcore = lcore_params[i].lcore_id;\n+\t\tnb_rx_queue = lcore_conf[lcore].n_rx_queue;\n+\t\tif (nb_rx_queue >= MAX_RX_QUEUE_PER_LCORE) {\n+\t\t\tprintf(\"Error: too many queues (%u) for lcore: %u\\n\",\n+\t\t\t       (unsigned int)nb_rx_queue + 1, (unsigned int)lcore);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tlcore_conf[lcore].rx_queue_list[nb_rx_queue].port_id = lcore_params[i].port_id;\n+\t\tlcore_conf[lcore].rx_queue_list[nb_rx_queue].queue_id = lcore_params[i].queue_id;\n+\t\tlcore_conf[lcore].n_rx_queue++;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+validate_config(void)\n+{\n+\tint rc = -1;\n+\n+\tif (check_lcore_params() < 0) {\n+\t\tprintf(\"check_lcore_params() failed\\n\");\n+\t\tgoto exit;\n+\t}\n+\n+\tif (init_lcore_rx_queues() < 0) {\n+\t\tprintf(\"init_lcore_rx_queues() failed\\n\");\n+\t\tgoto exit;\n+\t}\n+\n+\tif (check_port_config() < 0) {\n+\t\tprintf(\"check_port_config() failed\\n\");\n+\t\tgoto exit;\n+\t}\n+\n+\treturn 0;\n+\n+exit:\n+\treturn rc;\n+}\n+\n+#define MEMPOOL_CACHE_SIZE 256\n+\n+/*\n+ * This expression is used to calculate the number of mbufs needed\n+ * depending on user input, taking  into account memory for rx and\n+ * tx hardware rings, cache per lcore and mtable per port per lcore.\n+ * RTE_MAX is used to ensure that NB_MBUF never goes below a minimum\n+ * value of 8192\n+ */\n+#define NB_MBUF(nports)                                                                            \\\n+\tRTE_MAX((nports * nb_rx_queue * nb_rxd + nports * nb_lcores * RTE_GRAPH_BURST_SIZE +       \\\n+\t\t nports * nb_tx_queue * nb_txd + nb_lcores * MEMPOOL_CACHE_SIZE),                  \\\n+\t\t8192u)\n+\n+static int\n+init_mem(uint16_t portid, uint32_t nb_mbuf)\n+{\n+\tuint32_t lcore_id;\n+\tint socketid;\n+\tchar s[64];\n+\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\tif (rte_lcore_is_enabled(lcore_id) == 0)\n+\t\t\tcontinue;\n+\n+\t\tif (numa_on)\n+\t\t\tsocketid = rte_lcore_to_socket_id(lcore_id);\n+\t\telse\n+\t\t\tsocketid = 0;\n+\n+\t\tif (socketid >= NB_SOCKETS) {\n+\t\t\trte_exit(EXIT_FAILURE, \"Socket %d of lcore %u is out of range %d\\n\",\n+\t\t\t\t socketid, lcore_id, NB_SOCKETS);\n+\t\t}\n+\n+\t\tif (pktmbuf_pool[portid][socketid] == NULL) {\n+\t\t\tsnprintf(s, sizeof(s), \"mbuf_pool_%d:%d\", portid, socketid);\n+\t\t\t/* Create a pool with priv size of a cacheline */\n+\t\t\tpktmbuf_pool[portid][socketid] = rte_pktmbuf_pool_create(\n+\t\t\t\ts, nb_mbuf, MEMPOOL_CACHE_SIZE, RTE_CACHE_LINE_SIZE,\n+\t\t\t\tRTE_MBUF_DEFAULT_BUF_SIZE, socketid);\n+\t\t\tif (pktmbuf_pool[portid][socketid] == NULL)\n+\t\t\t\trte_exit(EXIT_FAILURE, \"Cannot init mbuf pool on socket %d\\n\",\n+\t\t\t\t\t socketid);\n+\t\t\telse\n+\t\t\t\tprintf(\"Allocated mbuf pool on socket %d\\n\", socketid);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void\n+check_all_ports_link_status(uint32_t port_mask)\n+{\n+#define CHECK_INTERVAL 100 /* 100ms */\n+#define MAX_CHECK_TIME 90  /* 9s (90 * 100ms) in total */\n+\tuint8_t count, all_ports_up, print_flag = 0;\n+\tstruct rte_eth_link link;\n+\tuint16_t portid;\n+\tint ret;\n+\tchar link_status_text[RTE_ETH_LINK_MAX_STR_LEN];\n+\n+\tprintf(\"\\nChecking link status\");\n+\tfflush(stdout);\n+\tfor (count = 0; count <= MAX_CHECK_TIME; count++) {\n+\t\tif (force_quit)\n+\t\t\treturn;\n+\t\tall_ports_up = 1;\n+\t\tRTE_ETH_FOREACH_DEV(portid) {\n+\t\t\tif (force_quit)\n+\t\t\t\treturn;\n+\t\t\tif ((port_mask & (1 << portid)) == 0)\n+\t\t\t\tcontinue;\n+\t\t\tmemset(&link, 0, sizeof(link));\n+\t\t\tret = rte_eth_link_get_nowait(portid, &link);\n+\t\t\tif (ret < 0) {\n+\t\t\t\tall_ports_up = 0;\n+\t\t\t\tif (print_flag == 1)\n+\t\t\t\t\tprintf(\"Port %u link get failed: %s\\n\", portid,\n+\t\t\t\t\t       rte_strerror(-ret));\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* Print link status if flag set */\n+\t\t\tif (print_flag == 1) {\n+\t\t\t\trte_eth_link_to_str(link_status_text, sizeof(link_status_text),\n+\t\t\t\t\t\t    &link);\n+\t\t\t\tprintf(\"Port %d %s\\n\", portid, link_status_text);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* Clear all_ports_up flag if any link down */\n+\t\t\tif (link.link_status == RTE_ETH_LINK_DOWN) {\n+\t\t\t\tall_ports_up = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\t/* After finally printing all link status, get out */\n+\t\tif (print_flag == 1)\n+\t\t\tbreak;\n+\n+\t\tif (all_ports_up == 0) {\n+\t\t\tprintf(\".\");\n+\t\t\tfflush(stdout);\n+\t\t\trte_delay_ms(CHECK_INTERVAL);\n+\t\t}\n+\n+\t\t/* Set the print_flag if all ports up or timeout */\n+\t\tif (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {\n+\t\t\tprint_flag = 1;\n+\t\t\tprintf(\"Done\\n\");\n+\t\t}\n+\t}\n+}\n+\n+static void\n+signal_handler(int signum)\n+{\n+\tif (signum == SIGINT || signum == SIGTERM) {\n+\t\tprintf(\"\\n\\nSignal %d received, preparing to exit...\\n\", signum);\n+\t\tforce_quit = true;\n+\t}\n+\tprompt_exit();\n+}\n+\n+int\n+graph_main_loop(void *conf)\n+{\n+\tstruct lcore_conf *qconf;\n+\tstruct rte_graph *graph;\n+\tuint32_t lcore_id;\n+\n+\tRTE_SET_USED(conf);\n+\n+\tlcore_id = rte_lcore_id();\n+\tqconf = &lcore_conf[lcore_id];\n+\tgraph = qconf->graph;\n+\n+\tif (!graph) {\n+\t\tRTE_LOG(INFO, TEST_GRAPH, \"Lcore %u has nothing to do\\n\", lcore_id);\n+\t\treturn 0;\n+\t}\n+\n+\tRTE_LOG(INFO, TEST_GRAPH, \"Entering main loop on lcore %u, graph %s(%p)\\n\", lcore_id,\n+\t\tqconf->name, graph);\n+\n+\twhile (likely(!force_quit && !graph_walk_quit)) {\n+\t\tif (likely(run_graph_walk))\n+\t\t\trte_graph_walk(graph);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+struct rte_graph_cluster_stats *\n+create_graph_cluster_stats(void)\n+{\n+\tstruct rte_graph_cluster_stats_param s_param;\n+\tstruct rte_graph_cluster_stats *stat;\n+\tconst char *pattern = \"worker_*\";\n+\n+\t/* Prepare stats object */\n+\tmemset(&s_param, 0, sizeof(s_param));\n+\ts_param.f = stdout;\n+\ts_param.socket_id = SOCKET_ID_ANY;\n+\ts_param.graph_patterns = &pattern;\n+\ts_param.nb_graph_patterns = 1;\n+\n+\tstat = rte_graph_cluster_stats_create(&s_param);\n+\tif (stat == NULL)\n+\t\trte_exit(EXIT_FAILURE, \"Unable to create stats object\\n\");\n+\n+\treturn stat;\n+}\n+\n+static void\n+print_stats(void)\n+{\n+\tconst char topLeft[] = {27, '[', '1', ';', '1', 'H', '\\0'};\n+\tconst char clr[] = {27, '[', '2', 'J', '\\0'};\n+\n+\twhile (!force_quit) {\n+\t\t/* Clear screen and move to top left */\n+\t\tprintf(\"%s%s\", clr, topLeft);\n+\t\trte_graph_cluster_stats_get(stats, 0);\n+\t\trte_delay_ms(1E3);\n+\t}\n+\n+\trte_graph_cluster_stats_destroy(stats);\n+}\n+\n+int\n+parse_config(const char *q_arg)\n+{\n+\tenum fieldnames { FLD_PORT = 0, FLD_QUEUE, FLD_LCORE, _NUM_FLD };\n+\tunsigned long int_fld[_NUM_FLD];\n+\tconst char *p, *p0 = q_arg;\n+\tchar *str_fld[_NUM_FLD];\n+\tuint32_t size;\n+\tchar s[256];\n+\tchar *end;\n+\tint i;\n+\n+\tnb_lcore_params = 0;\n+\n+\twhile ((p = strchr(p0, '(')) != NULL) {\n+\t\t++p;\n+\t\tp0 = strchr(p, ')');\n+\t\tif (p0 == NULL)\n+\t\t\tgoto exit;\n+\n+\t\tsize = p0 - p;\n+\t\tif (size >= sizeof(s))\n+\t\t\tgoto exit;\n+\n+\t\tmemcpy(s, p, size);\n+\t\ts[size] = '\\0';\n+\t\tif (rte_strsplit(s, sizeof(s), str_fld, _NUM_FLD, ',') != _NUM_FLD)\n+\t\t\tgoto exit;\n+\t\tfor (i = 0; i < _NUM_FLD; i++) {\n+\t\t\terrno = 0;\n+\t\t\tint_fld[i] = strtoul(str_fld[i], &end, 0);\n+\t\t\tif (errno != 0 || end == str_fld[i])\n+\t\t\t\tgoto exit;\n+\t\t}\n+\n+\t\tif (nb_lcore_params >= MAX_LCORE_PARAMS) {\n+\t\t\tprintf(\"Exceeded max number of lcore params: %hu\\n\", nb_lcore_params);\n+\t\t\tgoto exit;\n+\t\t}\n+\n+\t\tif (int_fld[FLD_PORT] >= RTE_MAX_ETHPORTS || int_fld[FLD_LCORE] >= RTE_MAX_LCORE) {\n+\t\t\tprintf(\"Invalid port/lcore id\\n\");\n+\t\t\tgoto exit;\n+\t\t}\n+\n+\t\tlcore_params_array[nb_lcore_params].port_id = (uint8_t)int_fld[FLD_PORT];\n+\t\tlcore_params_array[nb_lcore_params].queue_id = (uint8_t)int_fld[FLD_QUEUE];\n+\t\tlcore_params_array[nb_lcore_params].lcore_id = (uint8_t)int_fld[FLD_LCORE];\n+\t\t++nb_lcore_params;\n+\t}\n+\tlcore_params = lcore_params_array;\n+\n+\treturn 0;\n+exit:\n+\t/* Revert to default config */\n+\tlcore_params = lcore_params_array_default;\n+\tnb_lcore_params = RTE_DIM(lcore_params_array_default);\n+\n+\treturn -1;\n+}\n+\n+int\n+parse_node_patterns(const char *q_arg)\n+{\n+\tconst char *p, *p0 = q_arg;\n+\tint ret = -EINVAL;\n+\tuint32_t size;\n+\n+\tnum_patterns = 0;\n+\n+\tp = strchr(p0, '(');\n+\tif (p != NULL) {\n+\t\t++p;\n+\t\twhile ((p0 = strchr(p, ',')) != NULL) {\n+\t\t\tsize = p0 - p;\n+\t\t\tif (size >= RTE_NODE_NAMESIZE)\n+\t\t\t\tgoto exit;\n+\n+\t\t\tif (num_patterns >= MAX_NODE_PATTERNS) {\n+\t\t\t\tprintf(\"Too many nodes passed.\\n\");\n+\t\t\t\tgoto exit;\n+\t\t\t}\n+\n+\t\t\tmemcpy(node_pattern[num_patterns++], p, size);\n+\t\t\tp = p0 + 1;\n+\t\t}\n+\n+\t\tp0 = strchr(p, ')');\n+\t\tif (p0 != NULL) {\n+\t\t\tsize = p0 - p;\n+\t\t\tif (size >= RTE_NODE_NAMESIZE)\n+\t\t\t\tgoto exit;\n+\n+\t\t\tif (num_patterns >= MAX_NODE_PATTERNS) {\n+\t\t\t\tprintf(\"Too many nodes passed.\\n\");\n+\t\t\t\tgoto exit;\n+\t\t\t}\n+\n+\t\t\tmemcpy(node_pattern[num_patterns++], p, size);\n+\t\t} else {\n+\t\t\tgoto exit;\n+\t\t}\n+\t} else {\n+\t\tgoto exit;\n+\t}\n+\n+\treturn 0;\n+exit:\n+\treturn ret;\n+}\n+\n+static void\n+set_default_node_pattern(void)\n+{\n+\tuint16_t idx;\n+\n+\tfor (idx = 0; idx < test_node_list[0].size; idx++)\n+\t\tstrcpy(node_pattern[num_patterns++], test_node_list[0].nodes[idx]);\n+}\n+\n+int\n+validate_node_names(uint64_t *valid_nodes)\n+{\n+\trte_node_t node_cnt = rte_node_max_count();\n+\tbool pattern_matched = false;\n+\trte_node_t id = 0;\n+\tint ret = -EINVAL;\n+\tuint16_t idx, i, j;\n+\n+\tfor (idx = 0; idx < num_patterns; idx++) {\n+\t\tfor (id = 0; id < node_cnt; id++) {\n+\t\t\tif (strncmp(node_pattern[idx], rte_node_id_to_name(id),\n+\t\t\t\t    RTE_GRAPH_NAMESIZE) == 0)\n+\t\t\t\tbreak;\n+\t\t}\n+\t\tif (node_cnt == id) {\n+\t\t\tprintf(\"Invalid node name passed\\n\");\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < RTE_DIM(test_node_list); i++) {\n+\t\tidx = 0;\n+\t\tif (test_node_list[i].size == num_patterns) {\n+\t\t\tfor (j = 0; j < num_patterns; j++) {\n+\t\t\t\tif (strncmp(node_pattern[j], test_node_list[i].nodes[j],\n+\t\t\t\t    RTE_GRAPH_NAMESIZE) == 0)\n+\t\t\t\t\tidx++;\n+\t\t\t}\n+\t\t\tif (idx == num_patterns)\n+\t\t\t\tpattern_matched = true;\n+\t\t}\n+\t}\n+\n+\tif (!pattern_matched) {\n+\t\tprintf(\"Unsupported node pattern passed\\n\\n\");\n+\t\tprintf(\"Test supported node patterns are:\\n\");\n+\t\tfor (i = 0; i < RTE_DIM(test_node_list); i++) {\n+\t\t\tprintf(\"(\");\n+\t\t\tfor (j = 0; j < (test_node_list[i].size - 1); j++)\n+\t\t\t\tprintf(\"%s,\", test_node_list[i].nodes[j]);\n+\t\t\tprintf(\"%s\", test_node_list[i].nodes[j]);\n+\t\t\tprintf(\")\\n\");\n+\t\t}\n+\n+\t\treturn ret;\n+\t}\n+\n+\tfor (i = 0; i < RTE_DIM(supported_nodes); i++) {\n+\t\tfor (j = 0; j < num_patterns; j++) {\n+\t\t\tif (strncmp(node_pattern[j], supported_nodes[i].nodes[0],\n+\t\t\t\t    RTE_GRAPH_NAMESIZE) == 0) {\n+\t\t\t\t*valid_nodes |= supported_nodes[i].test_id;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void\n+cleanup_node_pattern(void)\n+{\n+\twhile (num_patterns) {\n+\t\tmemset(node_pattern[num_patterns - 1], 0, RTE_GRAPH_NAMESIZE);\n+\t\tnum_patterns--;\n+\t}\n+}\n+\n+static int\n+ethdev_tx_node_configure(void)\n+{\n+\tstruct ethdev_tx_node_main *tx_node_data;\n+\tstruct rte_node_register *tx_node;\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tuint16_t port_id;\n+\tuint32_t id;\n+\n+\ttx_node_data = ethdev_tx_node_data_get();\n+\ttx_node = ethdev_tx_node_get();\n+\n+\tRTE_ETH_FOREACH_DEV(port_id) {\n+\t\t/* Skip ports that are not enabled */\n+\t\tif ((enabled_port_mask & (1 << port_id)) == 0) {\n+\t\t\tprintf(\"\\nSkipping disabled port %d\\n\", port_id);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!rte_eth_dev_is_valid_port(port_id))\n+\t\t\treturn -EINVAL;\n+\n+\t\t/* Create a per port tx node from base node */\n+\t\tsnprintf(name, sizeof(name), \"%u\", port_id);\n+\t\tid = rte_node_clone(tx_node->id, name);\n+\t\ttx_node_data->nodes[port_id] = id;\n+\n+\t\tprintf(\"ethdev:: Tx node %s-%s: is at %u\\n\", tx_node->name, name, id);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+punt_kernel_node_configure(void)\n+{\n+\tstruct rte_node_register *punt_node;\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tstruct lcore_conf *qconf;\n+\tuint32_t lcore_id;\n+\tuint32_t id;\n+\n+\tpunt_node = punt_kernel_node_get();\n+\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\n+\t\tif (rte_lcore_is_enabled(lcore_id) == 0)\n+\t\t\tcontinue;\n+\n+\t\tqconf = &lcore_conf[lcore_id];\n+\n+\t\t/* Create a per lcore punt_kernel node from base node */\n+\t\tsnprintf(name, sizeof(name), \"%u\", lcore_id);\n+\t\tid = rte_node_clone(punt_node->id, name);\n+\t\tstrcpy(qconf->punt_kernel_node_name, rte_node_id_to_name(id));\n+\n+\t\tprintf(\"punt_kernel node %s-%s: is at %u\\n\", punt_node->name, name, id);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+kernel_recv_node_configure(void)\n+{\n+\tstruct rte_node_register *recv_node;\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tstruct lcore_conf *qconf;\n+\tuint32_t lcore_id;\n+\tuint32_t id;\n+\n+\trecv_node = kernel_recv_node_get();\n+\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\n+\t\tif (rte_lcore_is_enabled(lcore_id) == 0)\n+\t\t\tcontinue;\n+\n+\t\tqconf = &lcore_conf[lcore_id];\n+\n+\t\t/* Create a per lcore kernel_recv node from base node */\n+\t\tsnprintf(name, sizeof(name), \"%u\", lcore_id);\n+\t\tid = rte_node_clone(recv_node->id, name);\n+\t\tstrcpy(qconf->kernel_recv_node_name, rte_node_id_to_name(id));\n+\n+\t\tprintf(\"kernel_recv node %s-%s: is at %u\\n\", recv_node->name, name, id);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+ethdev_rx_node_configure(struct rte_node_ethdev_config *conf, uint16_t nb_confs)\n+{\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tuint16_t i, j, port_id;\n+\tuint32_t id;\n+\n+\tfor (i = 0; i < nb_confs; i++) {\n+\t\tport_id = conf[i].port_id;\n+\n+\t\tif (!rte_eth_dev_is_valid_port(port_id))\n+\t\t\treturn -EINVAL;\n+\n+\t\t/* Create node for each rx port queue pair */\n+\t\tfor (j = 0; j < conf[i].num_rx_queues; j++) {\n+\t\t\tstruct ethdev_rx_node_main *rx_node_data;\n+\t\t\tstruct rte_node_register *rx_node;\n+\t\t\tethdev_rx_node_elem_t *elem;\n+\n+\t\t\trx_node_data = ethdev_rx_get_node_data_get();\n+\t\t\trx_node = ethdev_rx_node_get();\n+\t\t\tsnprintf(name, sizeof(name), \"%u-%u\", port_id, j);\n+\t\t\t/* Clone a new rx node with same edges as parent */\n+\t\t\tid = rte_node_clone(rx_node->id, name);\n+\t\t\tif (id == RTE_NODE_ID_INVALID)\n+\t\t\t\treturn -EIO;\n+\n+\t\t\t/* Add it to list of ethdev rx nodes for lookup */\n+\t\t\telem = malloc(sizeof(ethdev_rx_node_elem_t));\n+\t\t\tif (elem == NULL)\n+\t\t\t\treturn -ENOMEM;\n+\t\t\tmemset(elem, 0, sizeof(ethdev_rx_node_elem_t));\n+\t\t\telem->ctx.port_id = port_id;\n+\t\t\telem->ctx.queue_id = j;\n+\t\t\telem->nid = id;\n+\t\t\telem->next = rx_node_data->head;\n+\t\t\trx_node_data->head = elem;\n+\n+\t\t\tprintf(\"ethdev:: Rx node %s-%s: is at %u\\n\", rx_node->name, name, id);\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+update_ethdev_rx_node_next(rte_node_t id, const char *edge_name)\n+{\n+\tstruct ethdev_rx_node_main *rx_node_data;\n+\tethdev_rx_node_elem_t *elem;\n+\tchar *next_nodes[16];\n+\trte_edge_t count;\n+\tuint16_t i;\n+\n+\tcount = rte_node_edge_count(id);\n+\trte_node_edge_get(id, next_nodes);\n+\n+\tfor (i = 0; i < count; i++) {\n+\t\tif (fnmatch(edge_name, next_nodes[i], 0) == 0) {\n+\t\t\trx_node_data = ethdev_rx_get_node_data_get();\n+\t\t\telem = rx_node_data->head;\n+\t\t\twhile (elem->next != rx_node_data->head) {\n+\t\t\t\tif (elem->nid == id)\n+\t\t\t\t\tbreak;\n+\t\t\t\telem = elem->next;\n+\t\t\t}\n+\n+\t\t\tif (elem->nid == id)\n+\t\t\t\telem->ctx.cls_next = i;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+link_ethdev_rx_to_tx_node(uint32_t lcore_id)\n+{\n+\tconst char * const pattern[] = {\"ethdev_tx-*\"};\n+\tuint16_t queue, queue_id, port_id;\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tconst char *next_node = name;\n+\tstruct lcore_conf *qconf;\n+\tuint32_t rx_id;\n+\n+\tqconf = &lcore_conf[lcore_id];\n+\n+\tfor (queue = 0; queue < qconf->n_rx_queue; ++queue) {\n+\t\tport_id = qconf->rx_queue_list[queue].port_id;\n+\t\tqueue_id = qconf->rx_queue_list[queue].queue_id;\n+\n+\t\tsnprintf(name, sizeof(name), \"ethdev_rx-%u-%u\", port_id, queue_id);\n+\t\trx_id = rte_node_from_name(name);\n+\n+\t\t/* Fill node pattern */\n+\t\tstrcpy(node_pattern[num_patterns++], name);\n+\n+\t\t/* Prepare the actual name of the cloned node */\n+\t\tsnprintf(name, sizeof(name), \"ethdev_tx-%u\", port_id);\n+\n+\t\t/* Update ethdev_rx node edges */\n+\t\trte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);\n+\n+\t\t/* Fill node pattern */\n+\t\tstrcpy(node_pattern[num_patterns++], name);\n+\n+\t\t/* Update node_next details */\n+\t\tupdate_ethdev_rx_node_next(rx_id, pattern[0]);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+link_ethdev_rx_to_punt_kernel_node(uint32_t lcore_id)\n+{\n+\tconst char * const pattern[] = {\"punt_kernel-*\"};\n+\tuint16_t queueid, portid, queue;\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tconst char *next_node = name;\n+\tstruct lcore_conf *qconf;\n+\trte_node_t rx_id;\n+\n+\tqconf = &lcore_conf[lcore_id];\n+\n+\tfor (queue = 0; queue < qconf->n_rx_queue; ++queue) {\n+\n+\t\tportid = qconf->rx_queue_list[queue].port_id;\n+\t\tqueueid = qconf->rx_queue_list[queue].queue_id;\n+\n+\t\tsnprintf(name, sizeof(name), \"ethdev_rx-%u-%u\", portid, queueid);\n+\t\trx_id = rte_node_from_name(name);\n+\n+\t\t/* Fill node pattern */\n+\t\tstrcpy(node_pattern[num_patterns++], name);\n+\n+\t\tnext_node = qconf->punt_kernel_node_name;\n+\n+\t\t/* Fill node pattern */\n+\t\tstrcpy(node_pattern[num_patterns++], next_node);\n+\n+\t\t/* Update ethdev_rx node edges */\n+\t\trte_node_edge_update(rx_id, RTE_EDGE_ID_INVALID, &next_node, 1);\n+\n+\t\t/* Update node_next details */\n+\t\tupdate_ethdev_rx_node_next(rx_id, pattern[0]);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+update_kernel_recv_node_next(rte_node_t id, const char *edge_name)\n+{\n+\tstruct kernel_recv_node_main *rx_node_data;\n+\tkernel_recv_node_elem_t *elem;\n+\tchar *next_nodes[16];\n+\trte_edge_t count;\n+\tuint16_t i;\n+\n+\tcount = rte_node_edge_count(id);\n+\trte_node_edge_get(id, next_nodes);\n+\n+\tfor (i = 0; i < count; i++) {\n+\t\tif (fnmatch(edge_name, next_nodes[i], 0) == 0) {\n+\t\t\trx_node_data = kernel_recv_node_data_get();\n+\t\t\telem = rx_node_data->head;\n+\t\t\twhile (elem->next != rx_node_data->head) {\n+\t\t\t\tif (elem->nid == id)\n+\t\t\t\t\tbreak;\n+\t\t\t\telem = elem->next;\n+\t\t\t}\n+\n+\t\t\tif (elem->nid == id)\n+\t\t\t\telem->ctx.recv_info->cls_next = i;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int\n+link_kernel_recv_to_ethdev_tx_node(uint32_t lcore_id)\n+{\n+\tconst char * const pattern[] = {\"ethdev_tx-*\"};\n+\tchar name[RTE_NODE_NAMESIZE];\n+\tconst char *next_node = name;\n+\tstruct lcore_conf *qconf;\n+\tuint16_t port_id;\n+\trte_node_t id;\n+\n+\tqconf = &lcore_conf[lcore_id];\n+\n+\tid = rte_node_from_name(qconf->kernel_recv_node_name);\n+\n+\t/* Fill node pattern */\n+\tstrcpy(node_pattern[num_patterns++], qconf->kernel_recv_node_name);\n+\n+\tport_id = lcore_id;\n+\n+\tif ((enabled_port_mask & (1 << port_id)) == 0) {\n+\t\t/* Use available port_id */\n+\t\tRTE_ETH_FOREACH_DEV(port_id) {\n+\t\t\tif ((enabled_port_mask & (1 << port_id)) != 0)\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (!rte_eth_dev_is_valid_port(port_id)) {\n+\t\tprintf(\"Port %u is not present on the board\\n\", port_id);\n+\t\treturn -1;\n+\t}\n+\n+\t/* Prepare the actual name of the cloned node */\n+\tsnprintf(name, sizeof(name), \"ethdev_tx-%u\", port_id);\n+\n+\t/* Update ethdev_rx node edges */\n+\trte_node_edge_update(id, RTE_EDGE_ID_INVALID, &next_node, 1);\n+\n+\t/* Fill node pattern */\n+\tstrcpy(node_pattern[num_patterns++], name);\n+\n+\t/* Update node_next details */\n+\tupdate_kernel_recv_node_next(id, pattern[0]);\n+\n+\treturn 0;\n+}\n+\n+uint32_t\n+ethdev_ports_setup(void)\n+{\n+\tstruct rte_eth_dev_info dev_info;\n+\tuint32_t nb_tx_queue, nb_lcores;\n+\tuint32_t nb_ports, nb_conf = 0;\n+\tuint8_t nb_rx_queue;\n+\tuint16_t portid;\n+\tint ret;\n+\n+\tnb_ports = rte_eth_dev_count_avail();\n+\tnb_lcores = rte_lcore_count();\n+\n+\tRTE_ETH_FOREACH_DEV(portid) {\n+\t\tstruct rte_eth_conf local_port_conf = port_conf;\n+\n+\t\t/* Skip ports that are not enabled */\n+\t\tif ((enabled_port_mask & (1 << portid)) == 0) {\n+\t\t\tprintf(\"\\nSkipping disabled port %d\\n\", portid);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* Init port */\n+\t\tprintf(\"Initializing port %d ... \", portid);\n+\t\tfflush(stdout);\n+\n+\t\tnb_rx_queue = get_port_n_rx_queues(portid);\n+\t\tnb_tx_queue = nb_lcores;\n+\t\tif (nb_tx_queue > MAX_TX_QUEUE_PER_PORT)\n+\t\t\tnb_tx_queue = MAX_TX_QUEUE_PER_PORT;\n+\t\tprintf(\"Creating queues: nb_rxq=%d nb_txq=%u...\\n\", nb_rx_queue, nb_tx_queue);\n+\n+\t\trte_eth_dev_info_get(portid, &dev_info);\n+\n+\t\tif (dev_info.tx_offload_capa & RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE)\n+\t\t\tlocal_port_conf.txmode.offloads |= RTE_ETH_TX_OFFLOAD_MBUF_FAST_FREE;\n+\n+\t\tlocal_port_conf.rx_adv_conf.rss_conf.rss_hf &= dev_info.flow_type_rss_offloads;\n+\t\tif (local_port_conf.rx_adv_conf.rss_conf.rss_hf !=\n+\t\t    port_conf.rx_adv_conf.rss_conf.rss_hf) {\n+\t\t\tprintf(\"Port %u modified RSS hash function based on hardware support,\"\n+\t\t\t       \"requested:%#\" PRIx64 \" configured:%#\" PRIx64 \"\\n\",\n+\t\t\t       portid, port_conf.rx_adv_conf.rss_conf.rss_hf,\n+\t\t\t       local_port_conf.rx_adv_conf.rss_conf.rss_hf);\n+\t\t}\n+\n+\t\tret = rte_eth_dev_configure(portid, nb_rx_queue, nb_tx_queue, &local_port_conf);\n+\t\tif (ret < 0)\n+\t\t\trte_exit(EXIT_FAILURE, \"Cannot configure device: err=%d, port=%d\\n\", ret,\n+\t\t\t\t portid);\n+\n+\t\tret = rte_eth_dev_adjust_nb_rx_tx_desc(portid, &nb_rxd, &nb_txd);\n+\t\tif (ret < 0)\n+\t\t\trte_exit(EXIT_FAILURE,\n+\t\t\t\t \"Cannot adjust number of descriptors: err=%d,\"\n+\t\t\t\t \"port=%d\\n\",\n+\t\t\t\t ret, portid);\n+\n+\t\t/* Init memory */\n+\t\tif (!per_port_pool)\n+\t\t\tret = init_mem(0, NB_MBUF(nb_ports));\n+\t\telse\n+\t\t\tret = init_mem(portid, NB_MBUF(1));\n+\n+\t\tif (ret < 0)\n+\t\t\trte_exit(EXIT_FAILURE, \"init_mem() failed\\n\");\n+\n+\t\t/* Setup ethdev node config */\n+\t\tethdev_conf[nb_conf].port_id = portid;\n+\t\tethdev_conf[nb_conf].num_rx_queues = nb_rx_queue;\n+\t\tethdev_conf[nb_conf].num_tx_queues = nb_tx_queue;\n+\t\tif (!per_port_pool)\n+\t\t\tethdev_conf[nb_conf].mp = pktmbuf_pool[0];\n+\n+\t\telse\n+\t\t\tethdev_conf[nb_conf].mp = pktmbuf_pool[portid];\n+\t\tethdev_conf[nb_conf].mp_count = NB_SOCKETS;\n+\n+\t\tnb_conf++;\n+\t}\n+\n+\treturn nb_conf;\n+}\n+\n+void\n+ethdev_rxq_configure(void)\n+{\n+\tstruct rte_eth_dev_info dev_info;\n+\tuint16_t queueid, portid;\n+\tstruct lcore_conf *qconf;\n+\tuint8_t queue, socketid;\n+\tuint32_t lcore_id;\n+\tint ret;\n+\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\tif (rte_lcore_is_enabled(lcore_id) == 0)\n+\t\t\tcontinue;\n+\t\tqconf = &lcore_conf[lcore_id];\n+\t\tprintf(\"\\nInitializing rx queues on lcore %u ... \", lcore_id);\n+\t\tfflush(stdout);\n+\n+\t\t/* Init RX queues */\n+\t\tfor (queue = 0; queue < qconf->n_rx_queue; ++queue) {\n+\t\t\tstruct rte_eth_rxconf rxq_conf;\n+\n+\t\t\tportid = qconf->rx_queue_list[queue].port_id;\n+\t\t\tqueueid = qconf->rx_queue_list[queue].queue_id;\n+\n+\t\t\tif (numa_on)\n+\t\t\t\tsocketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);\n+\t\t\telse\n+\t\t\t\tsocketid = 0;\n+\n+\t\t\tprintf(\"rxq=%d,%d,%d \", portid, queueid, socketid);\n+\t\t\tfflush(stdout);\n+\n+\t\t\trte_eth_dev_info_get(portid, &dev_info);\n+\t\t\trxq_conf = dev_info.default_rxconf;\n+\t\t\trxq_conf.offloads = port_conf.rxmode.offloads;\n+\t\t\tif (!per_port_pool)\n+\t\t\t\tret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,\n+\t\t\t\t\t\t\t     &rxq_conf, pktmbuf_pool[0][socketid]);\n+\t\t\telse\n+\t\t\t\tret = rte_eth_rx_queue_setup(portid, queueid, nb_rxd, socketid,\n+\t\t\t\t\t\t\t     &rxq_conf,\n+\t\t\t\t\t\t\t     pktmbuf_pool[portid][socketid]);\n+\t\t\tif (ret < 0)\n+\t\t\t\trte_exit(EXIT_FAILURE, \"rte_eth_rx_queue_setup: err=%d, port=%d\\n\",\n+\t\t\t\t\t ret, portid);\n+\n+\t\t\tsnprintf(qconf->rx_queue_list[queue].node_name, RTE_NODE_NAMESIZE,\n+\t\t\t\t \"ethdev_rx-%u-%u\", portid, queueid);\n+\t\t}\n+\t}\n+\tprintf(\"\\n\");\n+}\n+\n+void\n+ethdev_txq_configure(void)\n+{\n+\tstruct rte_eth_dev_info dev_info;\n+\tstruct rte_eth_txconf *txconf;\n+\tuint16_t queueid, portid;\n+\tuint32_t lcore_id;\n+\tuint8_t socketid;\n+\tint ret;\n+\n+\tRTE_ETH_FOREACH_DEV(portid) {\n+\t\tstruct rte_eth_conf local_port_conf = port_conf;\n+\n+\t\t/* Skip ports that are not enabled */\n+\t\tif ((enabled_port_mask & (1 << portid)) == 0) {\n+\t\t\tprintf(\"\\nSkipping disabled port %d\\n\", portid);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\trte_eth_dev_info_get(portid, &dev_info);\n+\n+\t\t/* Init one TX queue per (lcore,port) pair */\n+\t\tqueueid = 0;\n+\t\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\t\tif (rte_lcore_is_enabled(lcore_id) == 0)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (numa_on)\n+\t\t\t\tsocketid = (uint8_t)rte_lcore_to_socket_id(lcore_id);\n+\t\t\telse\n+\t\t\t\tsocketid = 0;\n+\n+\t\t\tprintf(\"txq=%u,%d,%d \", lcore_id, queueid, socketid);\n+\t\t\tfflush(stdout);\n+\n+\t\t\ttxconf = &dev_info.default_txconf;\n+\t\t\ttxconf->offloads = local_port_conf.txmode.offloads;\n+\t\t\tret = rte_eth_tx_queue_setup(portid, queueid, nb_txd, socketid, txconf);\n+\t\t\tif (ret < 0)\n+\t\t\t\trte_exit(EXIT_FAILURE, \"rte_eth_tx_queue_setup: err=%d, port=%d\\n\",\n+\t\t\t\t\t ret, portid);\n+\t\t\tqueueid++;\n+\t\t}\n+\t}\n+\tprintf(\"\\n\");\n+}\n+\n+int\n+configure_graph_nodes(uint64_t valid_nodes)\n+{\n+\tint ret = 0;\n+\n+\tif (valid_nodes & TEST_GRAPH_ETHDEV_RX_NODE) {\n+\t\tret = ethdev_rx_node_configure(ethdev_conf, nb_conf);\n+\t\tif (ret) {\n+\t\t\tprintf(\"ethdev_rx_node_configure: err=%d\\n\", ret);\n+\t\t\tgoto exit;\n+\t\t}\n+\t}\n+\n+\tif (valid_nodes & TEST_GRAPH_ETHDEV_TX_NODE) {\n+\t\tret = ethdev_tx_node_configure();\n+\t\tif (ret) {\n+\t\t\tprintf(\"ethdev_tx_node_configure: err=%d\\n\", ret);\n+\t\t\tgoto exit;\n+\t\t}\n+\t}\n+\n+\tif (valid_nodes & TEST_GRAPH_PUNT_KERNEL_NODE) {\n+\t\tret = punt_kernel_node_configure();\n+\t\tif (ret) {\n+\t\t\tprintf(\"punt_kernel_node_configure: err=%d\\n\", ret);\n+\t\t\tgoto exit;\n+\t\t}\n+\t}\n+\n+\tif (valid_nodes & TEST_GRAPH_KERNEL_RECV_NODE) {\n+\t\tret = kernel_recv_node_configure();\n+\t\tif (ret) {\n+\t\t\tprintf(\"kernel_recv_node_configure: err=%d\\n\", ret);\n+\t\t\tgoto exit;\n+\t\t}\n+\t}\n+\n+exit:\n+\treturn ret;\n+}\n+\n+static int\n+link_graph_nodes(uint64_t valid_nodes, uint32_t lcore_id)\n+{\n+\tint ret = 0;\n+\n+\tnum_patterns = 0;\n+\n+\tif (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE | TEST_GRAPH_ETHDEV_RX_NODE)) {\n+\t\tret = link_ethdev_rx_to_tx_node(lcore_id);\n+\t\tif (ret) {\n+\t\t\tprintf(\"link_ethdev_rx_to_tx_node: err=%d\\n\", ret);\n+\t\t\tgoto exit;\n+\t\t}\n+\t} else if (valid_nodes == (TEST_GRAPH_ETHDEV_RX_NODE | TEST_GRAPH_PUNT_KERNEL_NODE)) {\n+\t\tlink_ethdev_rx_to_punt_kernel_node(lcore_id);\n+\t} else if (valid_nodes == (TEST_GRAPH_ETHDEV_TX_NODE | TEST_GRAPH_KERNEL_RECV_NODE)) {\n+\t\tlink_kernel_recv_to_ethdev_tx_node(lcore_id);\n+\t} else {\n+\t\tprintf(\"Invalid node map\\n\");\n+\t\tret = -EINVAL;\n+\t}\n+\n+exit:\n+\treturn ret;\n+}\n+\n+void\n+start_eth_ports(void)\n+{\n+\tuint16_t portid;\n+\tint ret;\n+\n+\tRTE_ETH_FOREACH_DEV(portid) {\n+\t\tif ((enabled_port_mask & (1 << portid)) == 0)\n+\t\t\tcontinue;\n+\n+\t\tret = rte_eth_dev_start(portid);\n+\t\tif (ret < 0)\n+\t\t\trte_exit(EXIT_FAILURE, \"rte_eth_dev_start: err=%d, port=%d\\n\", ret, portid);\n+\n+\t\tif (promiscuous_on)\n+\t\t\trte_eth_promiscuous_enable(portid);\n+\t}\n+}\n+\n+void\n+stop_eth_ports(void)\n+{\n+\tuint16_t portid;\n+\tint ret;\n+\n+\tRTE_ETH_FOREACH_DEV(portid) {\n+\t\tif ((enabled_port_mask & (1 << portid)) == 0)\n+\t\t\tcontinue;\n+\t\tprintf(\"Closing port %d...\", portid);\n+\t\tret = rte_eth_dev_stop(portid);\n+\t\tif (ret != 0)\n+\t\t\tprintf(\"Failed to stop port %u: %s\\n\", portid, rte_strerror(-ret));\n+\t\trte_eth_dev_close(portid);\n+\t}\n+}\n+\n+int\n+create_graph(uint64_t valid_nodes)\n+{\n+\tstruct rte_graph_param graph_conf;\n+\tstruct lcore_conf *qconf;\n+\tuint32_t lcore_id;\n+\tint ret;\n+\n+\tnode_patterns = malloc(MAX_NODE_PATTERNS * sizeof(*node_patterns));\n+\tif (!node_patterns)\n+\t\treturn -ENOMEM;\n+\n+\tmemset(&graph_conf, 0, sizeof(graph_conf));\n+\tgraph_conf.node_patterns = node_patterns;\n+\n+\tfor (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {\n+\t\trte_graph_t graph_id;\n+\t\trte_edge_t i;\n+\n+\t\tif (rte_lcore_is_enabled(lcore_id) == 0)\n+\t\t\tcontinue;\n+\n+\t\tqconf = &lcore_conf[lcore_id];\n+\n+\t\t/* Skip graph creation if no source exists */\n+\t\tif (!qconf->n_rx_queue)\n+\t\t\tcontinue;\n+\n+\t\tret = link_graph_nodes(valid_nodes, lcore_id);\n+\t\tif (ret)\n+\t\t\trte_exit(EXIT_FAILURE, \"link_graph_nodes(): failed\\n\");\n+\n+\t\tfor (i = 0; i < num_patterns; i++) {\n+\t\t\tgraph_conf.node_patterns[i] = node_pattern[i];\n+\t\t\tprintf(\"%s,\", graph_conf.node_patterns[i]);\n+\t\t}\n+\t\tprintf(\"\\n\");\n+\n+\t\tgraph_conf.nb_node_patterns = num_patterns;\n+\n+\t\tgraph_conf.socket_id = rte_lcore_to_socket_id(lcore_id);\n+\n+\t\tsnprintf(qconf->name, sizeof(qconf->name), \"worker_%u\", lcore_id);\n+\n+\t\tgraph_id = rte_graph_create(qconf->name, &graph_conf);\n+\t\tif (graph_id == RTE_GRAPH_ID_INVALID)\n+\t\t\trte_exit(EXIT_FAILURE, \"rte_graph_create(): graph_id invalid for lcore%u\\n\",\n+\t\t\t\t lcore_id);\n+\n+\t\tqconf->graph_id = graph_id;\n+\t\tqconf->graph = rte_graph_lookup(qconf->name);\n+\n+\t\tif (!qconf->graph)\n+\t\t\trte_exit(EXIT_FAILURE, \"rte_graph_lookup(): graph %s not found\\n\",\n+\t\t\t\t qconf->name);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int\n+destroy_graph(void)\n+{\n+\tuint32_t lcore_id;\n+\trte_graph_t id;\n+\tint ret;\n+\n+\tRTE_LCORE_FOREACH_WORKER(lcore_id)\n+\t{\n+\t\tif (lcore_conf[lcore_id].graph) {\n+\t\t\tid = rte_graph_from_name(lcore_conf[lcore_id].name);\n+\t\t\tif (rte_graph_destroy(id)) {\n+\t\t\t\tprintf(\"graph_id %u destroy failed.\\n\", id);\n+\t\t\t\tret = -1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (node_patterns)\n+\t\tfree(node_patterns);\n+\n+\treturn ret;\n+}\n+\n+int\n+main(int argc, char **argv)\n+{\n+\tuint64_t valid_nodes;\n+\tuint32_t lcore_id;\n+\tint ret;\n+\n+\tgraph_walk_quit = false;\n+\tforce_quit = false;\n+\tinteractive = 0;\n+\n+\tnode_patterns = NULL;\n+\n+\tsignal(SIGINT, signal_handler);\n+\tsignal(SIGTERM, signal_handler);\n+\n+\ttestgraph_logtype = rte_log_register(\"testgraph\");\n+\tif (testgraph_logtype < 0)\n+\t\trte_exit(EXIT_FAILURE, \"Cannot register log type\");\n+\n+\tset_default_node_pattern();\n+\n+\trte_log_set_level(testgraph_logtype, RTE_LOG_DEBUG);\n+\n+\tret = rte_eal_init(argc, argv);\n+\tif (ret < 0)\n+\t\trte_exit(EXIT_FAILURE, \"Cannot init EAL: %s\\n\", rte_strerror(rte_errno));\n+\targc -= ret;\n+\targv += ret;\n+\n+\tif (argc > 1) {\n+\t\tret = parse_cmdline_args(argc, argv);\n+\t\tif (ret < 0)\n+\t\t\trte_exit(EXIT_FAILURE, \"Invalid command line parameters\\n\");\n+\t}\n+\n+#ifdef RTE_LIB_CMDLINE\n+\tif (init_cmdline() != 0)\n+\t\trte_exit(EXIT_FAILURE, \"Could not initialise cmdline context.\\n\");\n+\n+\tif (interactive == 1) {\n+\t\tprompt();\n+\t} else\n+#endif\n+\t{\n+\t\tif (validate_config() < 0)\n+\t\t\trte_exit(EXIT_FAILURE, \"Config validation failed.\\n\");\n+\n+\t\tret = validate_node_names(&valid_nodes);\n+\t\tif (ret)\n+\t\t\trte_exit(EXIT_FAILURE, \"validate_node_names: err=%d\\n\", ret);\n+\n+\t\tnb_conf = ethdev_ports_setup();\n+\n+\t\tethdev_rxq_configure();\n+\n+\t\tethdev_txq_configure();\n+\n+\t\tret = configure_graph_nodes(valid_nodes);\n+\t\tif (ret)\n+\t\t\trte_exit(EXIT_FAILURE, \"configure_graph_nodes: err=%d\\n\", ret);\n+\n+\t\tret = create_graph(valid_nodes);\n+\t\tif (ret)\n+\t\t\trte_exit(EXIT_FAILURE, \"create_graph: err=%d\\n\", ret);\n+\n+\t\tstats = create_graph_cluster_stats();\n+\t\tif (stats == NULL)\n+\t\t\trte_exit(EXIT_FAILURE, \"create_graph_cluster_stats() failed\\n\");\n+\n+\t\tcheck_all_ports_link_status(enabled_port_mask);\n+\n+\t\tstart_eth_ports();\n+\n+\t\t/* Launch per-lcore init on every worker lcore */\n+\t\trte_eal_mp_remote_launch(graph_main_loop, NULL, SKIP_MAIN);\n+\n+\t\t/* Accumulate and print stats on main until exit */\n+\t\tif (rte_graph_has_stats_feature())\n+\t\t\tprint_stats();\n+\n+\t\t/* Wait for worker cores to exit */\n+\t\tRTE_LCORE_FOREACH_WORKER(lcore_id) {\n+\t\t\tret = rte_eal_wait_lcore(lcore_id);\n+\t\t\tif (ret < 0)\n+\t\t\t\tbreak;\n+\t\t}\n+\n+\t\tret = destroy_graph();\n+\n+\t\tstop_eth_ports();\n+\t}\n+\n+\t/* clean up the EAL */\n+\tret = rte_eal_cleanup();\n+\tif (ret != 0)\n+\t\trte_exit(EXIT_FAILURE, \"EAL cleanup failed: %s\\n\", strerror(-ret));\n+\n+\treturn EXIT_SUCCESS;\n+}\ndiff --git a/app/test-graph/testgraph.h b/app/test-graph/testgraph.h\nnew file mode 100644\nindex 0000000000..ea1f8ef4fa\n--- /dev/null\n+++ b/app/test-graph/testgraph.h\n@@ -0,0 +1,91 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(C) 2023 Marvell International Ltd.\n+ */\n+\n+#ifndef _TESTGRAPH_H_\n+#define _TESTGRAPH_H_\n+\n+#include <stdbool.h>\n+\n+#include <rte_ethdev.h>\n+#include <rte_graph.h>\n+#include <rte_node_eth_api.h>\n+\n+#include <cmdline.h>\n+#include <cmdline_parse.h>\n+\n+\n+#define MAX_LCORE_PARAMS 1024\n+#define MAX_NODE_PATTERNS 128\n+\n+#ifndef BIT_ULL\n+#define BIT_ULL(nr) (1ULL << (nr))\n+#endif\n+\n+#define TEST_GRAPH_ETHDEV_RX_NODE BIT_ULL(0)\n+#define TEST_GRAPH_ETHDEV_TX_NODE BIT_ULL(1)\n+#define TEST_GRAPH_PUNT_KERNEL_NODE BIT_ULL(2)\n+#define TEST_GRAPH_KERNEL_RECV_NODE BIT_ULL(3)\n+#define TEST_GRAPH_IP4_LOOKUP_NODE BIT_ULL(4)\n+#define TEST_GRAPH_IP4_REWRITE_NODE BIT_ULL(5)\n+#define TEST_GRAPH_PKT_CLS_NODE BIT_ULL(6)\n+#define TEST_GRAPH_PKT_DROP_NODE BIT_ULL(7)\n+#define TEST_GRAPH_NULL_NODE BIT_ULL(8)\n+\n+static volatile bool force_quit;\n+\n+extern struct rte_node_ethdev_config ethdev_conf[RTE_MAX_ETHPORTS];\n+extern uint32_t enabled_port_mask;\n+extern uint32_t nb_conf;\n+\n+extern int promiscuous_on;\t   /**< Ports set in promiscuous mode off by default. */\n+extern uint8_t interactive;\t   /**< interactive mode is disabled by default. */\n+extern uint32_t enabled_port_mask; /**< Mask of enabled ports */\n+extern int numa_on;\t\t   /**< NUMA is enabled by default. */\n+extern int per_port_pool;\n+\n+extern volatile bool graph_walk_quit;\n+extern volatile bool run_graph_walk;\n+extern struct rte_graph_cluster_stats *stats;\n+\n+extern char node_pattern[MAX_NODE_PATTERNS][RTE_NODE_NAMESIZE];\n+extern uint8_t num_patterns;\n+\n+struct node_list {\n+\tconst char *nodes[MAX_NODE_PATTERNS];\n+\tuint64_t test_id;\n+\tuint8_t size;\n+};\n+\n+struct lcore_params {\n+\tuint16_t port_id;\n+\tuint8_t queue_id;\n+\tuint8_t lcore_id;\n+} __rte_cache_aligned;\n+\n+void prompt(void);\n+void prompt_exit(void);\n+int init_cmdline(void);\n+int validate_config(void);\n+int parse_cmdline_args(int argc, char **argv);\n+uint32_t ethdev_ports_setup(void);\n+void ethdev_rxq_configure(void);\n+void ethdev_txq_configure(void);\n+void start_eth_ports(void);\n+void stop_eth_ports(void);\n+int create_graph(uint64_t valid_nodes);\n+int destroy_graph(void);\n+int graph_main_loop(void *conf);\n+struct rte_graph_cluster_stats *create_graph_cluster_stats(void);\n+void check_all_ports_link_status(uint32_t port_mask);\n+int configure_graph_nodes(uint64_t valid_nodes);\n+\n+int parse_config(const char *q_arg);\n+int parse_node_patterns(const char *q_arg);\n+int validate_node_names(uint64_t *valid_nodes);\n+void cleanup_node_pattern(void);\n+\n+#define TESTGRAPH_LOG(level, fmt, args...)                                                         \\\n+\trte_log(RTE_LOG_##level, testgraph_logtype, \"testgraph: \" fmt, ##args)\n+\n+#endif /* _TESTGRAPH_H_ */\ndiff --git a/doc/guides/tools/index.rst b/doc/guides/tools/index.rst\nindex 6f84fc31ff..f18c508fa2 100644\n--- a/doc/guides/tools/index.rst\n+++ b/doc/guides/tools/index.rst\n@@ -20,6 +20,7 @@ DPDK Tools User Guides\n     cryptoperf\n     comp_perf\n     testeventdev\n+    testgraph\n     testregex\n     testmldev\n     dts\ndiff --git a/doc/guides/tools/testgraph.rst b/doc/guides/tools/testgraph.rst\nnew file mode 100644\nindex 0000000000..3c1e058724\n--- /dev/null\n+++ b/doc/guides/tools/testgraph.rst\n@@ -0,0 +1,131 @@\n+..  SPDX-License-Identifier: BSD-3-Clause\n+    Copyright(C) 2023 Marvell International Ltd.\n+\n+dpdk-test-graph Application\n+===========================\n+\n+The ``dpdk-test-graph`` tool is a Data Plane Development Kit (DPDK) application that allows\n+exercising various graph library features. This application has a generic framework to add\n+new test configurations and expand test coverage to verify the functionality of graph nodes\n+and observe the graph cluster statistics.\n+\n+Running the Application\n+-----------------------\n+\n+The application has a number of command line options:\n+\n+.. code-block:: console\n+\n+   dpdk-test-eventdev [EAL Options] -- [application options]\n+\n+EAL Options\n+~~~~~~~~~~~\n+\n+The following are the EAL command-line options that can be used in conjunction\n+with the ``dpdk-test-graph`` application.\n+See the DPDK Getting Started Guides for more information on these options.\n+\n+*   ``-c <COREMASK>`` or ``-l <CORELIST>``\n+\n+        Set the hexadecimal bitmask of the cores to run on. The corelist is a\n+        list of cores to use.\n+\n+Application Options\n+~~~~~~~~~~~~~~~~~~~\n+\n+The following are the application command-line options:\n+\n+* ``-p <n>``\n+\n+        Set the ethdev port mask.\n+\n+* ``-P``\n+\n+        Set the ethdev ports in promiscuous mode.\n+\n+* ``--config <config>``\n+\n+        Set the Rxq configuration.\n+        (i.e. ``--config (port_id,rxq,lcore_id)[,(port_id,rxq,lcore_id)]``).\n+\n+* ``--node-pattern <n>``\n+\n+        Set the node patterns to use in graph creation.\n+        (i.e. ``--node-pattern (node_name0,node_name1[,node_nameX])``).\n+\n+* ``--per-port-pool``\n+\n+        Use separate buffer pool per port.\n+\n+* ``--no-numa``\n+\n+        Disable numa awareness.\n+\n+* ``--interactive``\n+\n+        Switch to interactive mode.\n+\n+Running the Tool\n+~~~~~~~~~~~~~~~~\n+\n+Here is the sample command line to run simple iofwd test::\n+\n+       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \\\n+       --config \"(0,0,2),(1,0,2)\" --node-pattern \"(ethdev_rx,ethdev_tx)\"\n+\n+Below is a sample command line to punt rx packets to kernel::\n+\n+       ./dpdk-test-graph -a 0002:03:00.0 -a 0002:04:00.0 -c 0xF  -- -p 0x3 -P  \\\n+       --config \"(0,0,2),(1,0,2)\" --node-pattern \"(ethdev_rx,punt_kernel)\"\n+\n+Interactive mode\n+~~~~~~~~~~~~~~~~\n+\n+Tool uses ``--interactive`` command line option to enter interactive mode and use cmdline options\n+to setup the required node configurations, create graph and than start graph_walk.\n+\n+\n+testgraph> help\n+\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+testgraph> help all\n+\n+Control forwarding:\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+\n+Display:\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+\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+testgraph>\n",
    "prefixes": [
        "v2",
        "4/4"
    ]
}