Show a patch.

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

{
    "id": 73432,
    "url": "https://patches.dpdk.org/api/patches/73432/",
    "web_url": "https://patches.dpdk.org/patch/73432/",
    "project": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/projects/1/",
        "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"
    },
    "msgid": "<20200707141108.10790-3-felix.moessbauer@siemens.com>",
    "date": "2020-07-07T14:11:08",
    "name": "[v2,2/2] Add l2reflect measurement application",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "f8c8a07e4d8d21abb06dec5d40c168799410d3b0",
    "submitter": {
        "id": 1811,
        "url": "https://patches.dpdk.org/api/people/1811/",
        "name": "Felix Moessbauer",
        "email": "felix.moessbauer@siemens.com"
    },
    "delegate": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/users/1/",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "https://patches.dpdk.org/patch/73432/mbox/",
    "series": [
        {
            "id": 10851,
            "url": "https://patches.dpdk.org/api/series/10851/",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=10851",
            "date": "2020-07-07T14:11:06",
            "name": "Add l2reflect measurement application",
            "version": 2,
            "mbox": "https://patches.dpdk.org/series/10851/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/73432/comments/",
    "check": "fail",
    "checks": "https://patches.dpdk.org/api/patches/73432/checks/",
    "tags": {},
    "headers": {
        "X-Mailman-Version": "2.1.15",
        "Date": "Tue,  7 Jul 2020 16:11:08 +0200",
        "In-Reply-To": "<20200703151811.38372-2-felix.moessbauer@siemens.com>",
        "Errors-To": "dev-bounces@dpdk.org",
        "X-Mailer": "git-send-email 2.20.1",
        "Received": [
            "from dpdk.org (dpdk.org [92.243.14.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 05B14A00BE;\n\tTue,  7 Jul 2020 16:12:14 +0200 (CEST)",
            "from [92.243.14.124] (localhost [127.0.0.1])\n\tby dpdk.org (Postfix) with ESMTP id 4A8E21DE37;\n\tTue,  7 Jul 2020 16:11:56 +0200 (CEST)",
            "from lizzard.sbs.de (lizzard.sbs.de [194.138.37.39])\n by dpdk.org (Postfix) with ESMTP id 3880E1DE34\n for <dev@dpdk.org>; Tue,  7 Jul 2020 16:11:55 +0200 (CEST)",
            "from mail2.sbs.de (mail2.sbs.de [192.129.41.66])\n by lizzard.sbs.de (8.15.2/8.15.2) with ESMTPS id 067EBsli018477\n (version=TLSv1.2 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK);\n Tue, 7 Jul 2020 16:11:54 +0200",
            "from derbion.ppmd.siemens.net (derbion.ppmd.siemens.net\n [139.25.68.102])\n by mail2.sbs.de (8.15.2/8.15.2) with ESMTP id 067EBaX2000464;\n Tue, 7 Jul 2020 16:11:54 +0200"
        ],
        "References": "<20200703151811.38372-2-felix.moessbauer@siemens.com>",
        "MIME-Version": "1.0",
        "Message-Id": "<20200707141108.10790-3-felix.moessbauer@siemens.com>",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "Precedence": "list",
        "From": "Felix Moessbauer <felix.moessbauer@siemens.com>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "List-Post": "<mailto:dev@dpdk.org>",
        "Return-Path": "<dev-bounces@dpdk.org>",
        "Sender": "\"dev\" <dev-bounces@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>",
        "To": "thomas@monjalon.net",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "X-BeenThere": "dev@dpdk.org",
        "Content-Transfer-Encoding": "8bit",
        "Cc": "dev@dpdk.org, Felix Moessbauer <felix.moessbauer@siemens.com>,\n Henning Schild <henning.schild@siemens.com>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "Subject": "[dpdk-dev] [PATCH v2 2/2] Add l2reflect measurement application"
    },
    "content": "The l2reflect application implements a ping-pong benchmark to\nmeasure the latency between two instances. For communication,\nwe use raw ethernet and send one packet at a time. The timing data\nis collected locally and min/max/avg values are displayed in a TUI.\nFinally, a histogram of the latencies is printed which can be\nfurther processed with the jitterdebugger visualization scripts.\nTo debug latency spikes, a max threshold can be defined.\nIf it is hit, a trace point is created on both instances.\n\nSigned-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>\nSigned-off-by: Henning Schild <henning.schild@siemens.com>\n---\n app/Makefile              |   1 +\n app/l2reflect/Makefile    |  31 ++\n app/l2reflect/l2reflect.h |  62 +++\n app/l2reflect/main.c      | 857 ++++++++++++++++++++++++++++++++++++++\n app/l2reflect/meson.build |  21 +\n app/l2reflect/stats.c     | 201 +++++++++\n app/l2reflect/stats.h     |  67 +++\n app/meson.build           |   1 +\n 8 files changed, 1241 insertions(+)\n create mode 100644 app/l2reflect/Makefile\n create mode 100644 app/l2reflect/l2reflect.h\n create mode 100644 app/l2reflect/main.c\n create mode 100644 app/l2reflect/meson.build\n create mode 100644 app/l2reflect/stats.c\n create mode 100644 app/l2reflect/stats.h",
    "diff": "diff --git a/app/Makefile b/app/Makefile\nindex 0392a7de0..43563ef0e 100644\n--- a/app/Makefile\n+++ b/app/Makefile\n@@ -7,6 +7,7 @@ DIRS-$(CONFIG_RTE_APP_TEST) += test\n DIRS-$(CONFIG_RTE_TEST_PMD) += test-pmd\n DIRS-$(CONFIG_RTE_PROC_INFO) += proc-info\n DIRS-$(CONFIG_RTE_LIBRTE_PDUMP) += pdump\n+DIRS-$(CONFIG_RTE_LIBRTE_L2REFLECT) += l2reflect\n DIRS-$(CONFIG_RTE_LIBRTE_ACL) += test-acl\n DIRS-$(CONFIG_RTE_LIBRTE_CMDLINE) += test-cmdline\n DIRS-$(CONFIG_RTE_LIBRTE_FIB) += test-fib\ndiff --git a/app/l2reflect/Makefile b/app/l2reflect/Makefile\nnew file mode 100644\nindex 000000000..4f3e74236\n--- /dev/null\n+++ b/app/l2reflect/Makefile\n@@ -0,0 +1,31 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2020 Siemens AG\n+\n+APP = l2reflect\n+SOURCES := main.c stats.c\n+OBJECTS = $(SOURCES:%.c=%.o)\n+\n+ifneq ($(RTE_SDK),)\n+\tRTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config)))))\n+\tinclude $(RTE_SDK)/mk/rte.vars.mk\n+\tSRCS-y := $(SOURCES)\n+\tCFLAGS += -O3 -Wall -Wpedantic -Werror\n+else\n+\tCFLAGS += -O3 $(shell pkg-config --cflags libdpdk)\n+\tLDFLAGS += -Wl,-static $(shell pkg-config --static --libs libdpdk)\n+endif\n+CFLAGS += -DRTE_HAS_CJSON -Wstrict-prototypes -Wsign-compare -Wunused-parameter -Wunused-parameter\n+LDFLAGS += -lcjson\n+\n+ifeq ($(RTE_SDK),)\n+%.o: %.c Makefile\n+\t$(CC) -c -o $@ $< $(CFLAGS)\n+\n+$(APP): $(OBJECTS) Makefile\n+\t$(CC) -o $@ $(OBJECTS) $(LDFLAGS)\n+\n+clean:\n+\trm -f $(APP) $(OBJECTS)\n+else\n+\tinclude $(RTE_SDK)/mk/rte.extapp.mk\n+endif\ndiff --git a/app/l2reflect/l2reflect.h b/app/l2reflect/l2reflect.h\nnew file mode 100644\nindex 000000000..372dfb1f2\n--- /dev/null\n+++ b/app/l2reflect/l2reflect.h\n@@ -0,0 +1,62 @@\n+/*\n+ * SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2020 Siemens AG\n+ *\n+ * authors:\n+ *   Felix Moessbauer <felix.moessbauer@siemens.com>\n+ *   Henning Schild <henning.schild@siemens.com>\n+ */\n+#ifndef _L2REFLECT_H_\n+#define _L2REFLECT_H_\n+#define MAGIC_TRACE_PAYLOAD 0xd00faffeaffed00full\n+/* IEEE Std 802 - Local Experimental Ethertype */\n+#define ETHER_TYPE_L2REFLECT 0x88B5\n+/* Used to compare MAC addresses. */\n+#define MAC_ADDR_CMP 0xFFFFFFFFFFFFull\n+\n+enum {\n+\tTRACE_TYPE_DATA,\n+\tTRACE_TYPE_HELO,\n+\tTRACE_TYPE_EHLO,\n+\tTRACE_TYPE_RSET,\n+\tTRACE_TYPE_QUIT,\n+};\n+\n+struct my_magic_packet {\n+\tstruct rte_ether_hdr eth;\n+\tuint8_t type;\n+\tuint64_t magic;\n+\tuint64_t breakval;\n+};\n+\n+enum STATE {\n+\t/* elect the initial sender */\n+\tS_ELECT_LEADER = 1,\n+\t/* reset the counters */\n+\tS_RESET_TRX = 2,\n+\t/* measurement S_RUNNING */\n+\tS_RUNNING = 4,\n+\t/* terminated by local event */\n+\tS_LOCAL_TERM = 8,\n+\t/* terminated by remote event */\n+\tS_REMOTE_TERM = 16\n+};\n+\n+int l2reflect_hist;\n+atomic_int l2reflect_output_hist;\n+uint64_t l2reflect_sleep_msec;\n+uint16_t l2reflect_port_number;\n+atomic_int l2reflect_state;\n+struct rte_ether_addr l2reflect_port_eth_addr;\n+struct rte_ether_addr l2reflect_remote_eth_addr;\n+\n+/*\n+ * Compares a packet destination MAC address to a device MAC address.\n+ */\n+static __rte_always_inline int\n+ether_addr_cmp(struct rte_ether_addr *ea, struct rte_ether_addr *eb)\n+{\n+\treturn ((*(uint64_t *)ea ^ *(uint64_t *)eb) & MAC_ADDR_CMP) == 0;\n+}\n+\n+#endif /* _L2REFLECT_H_ */\ndiff --git a/app/l2reflect/main.c b/app/l2reflect/main.c\nnew file mode 100644\nindex 000000000..2cf93cdec\n--- /dev/null\n+++ b/app/l2reflect/main.c\n@@ -0,0 +1,857 @@\n+/*\n+ * SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2020 Siemens AG\n+ *\n+ * authors:\n+ *   Felix Moessbauer <felix.moessbauer@siemens.com>\n+ *   Henning Schild <henning.schild@siemens.com>\n+ *\n+ * launch (non-rt kernel): l2reflect --lcores 0@0,1@6 -n 1\n+ * launch (rt kernel): l2reflect --lcores 0@0,1@6 -n 1 -- -P 50 -r -l\n+ *\n+ * The l2reflect application implements a ping-pong benchmark to\n+ * measure the latency between two instances. For communication,\n+ * we use raw ethernet and send one packet at a time. The timing data\n+ * is collected locally and min/max/avg values are displayed in a TUI.\n+ * Finally, a histogram of the latencies is printed which can be\n+ * further processed with the jitterdebugger visualization scripts.\n+ * To debug latency spikes, a max threshold can be defined.\n+ * If it is hit, a trace point is created on both instances.\n+ */\n+\n+#include <stdio.h>\n+#include <errno.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <stdint.h>\n+#include <inttypes.h>\n+#include <getopt.h>\n+#include <sys/signal.h>\n+#include <assert.h>\n+#include <unistd.h>\n+#include <sys/io.h>\n+#include <sched.h>\n+#include <sys/mman.h>\n+#include <sys/types.h>\n+#include <sys/stat.h>\n+#include <fcntl.h>\n+#include <stdatomic.h>\n+\n+#include <rte_common.h>\n+#include <rte_errno.h>\n+#include <rte_log.h>\n+#include <rte_memory.h>\n+#include <rte_memcpy.h>\n+#include <rte_memzone.h>\n+#include <rte_eal.h>\n+#include <rte_per_lcore.h>\n+#include <rte_launch.h>\n+#include <rte_atomic.h>\n+#include <rte_cycles.h>\n+#include <rte_prefetch.h>\n+#include <rte_lcore.h>\n+#include <rte_per_lcore.h>\n+#include <rte_branch_prediction.h>\n+#include <rte_interrupts.h>\n+#include <rte_random.h>\n+#include <rte_debug.h>\n+#include <rte_ether.h>\n+#include <rte_ethdev.h>\n+#include <rte_ring.h>\n+#include <rte_mempool.h>\n+#include <rte_mbuf.h>\n+\n+#include \"l2reflect.h\"\n+#include \"stats.h\"\n+\n+#define RTE_LOGTYPE_L2REFLECT RTE_LOGTYPE_USER1\n+\n+#define NSEC_PER_SEC 1000000000\n+\n+#define MBUF_SIZE (2048 + sizeof(struct rte_mbuf) + RTE_PKTMBUF_HEADROOM)\n+#define NB_MBUF 2047\n+\n+#define MAX_PKT_BURST 32\n+/* warmup a few round before starting the measurement */\n+#define WARMUP_ROUNDS 42\n+\n+#define MAX(x, y) (((x) > (y)) ? (x) : (y))\n+#define MIN(x, y) (((x) < (y)) ? (x) : (y))\n+\n+static struct timespec last_sent, last_recv;\n+static int quiet, disable_int, priority, policy, l2reflect_mlock,\n+\tl2reflect_interrupt, trace_fd;\n+\n+static atomic_int sleep_start;\n+\n+static uint64_t rounds;\n+static int quiet, disable_int, priority, policy, l2reflect_mlock,\n+\tl2reflect_interrupt, trace_fd;\n+\n+/* Configurable number of RX/TX ring descriptors */\n+#define RTE_TEST_RX_DESC_DEFAULT 128\n+#define RTE_TEST_TX_DESC_DEFAULT 512\n+static uint16_t nb_rxd = RTE_TEST_RX_DESC_DEFAULT;\n+static uint16_t nb_txd = RTE_TEST_TX_DESC_DEFAULT;\n+\n+static struct rte_eth_conf port_conf = {\n+\t.rxmode = {\n+\t\t.split_hdr_size = 0,\n+\t},\n+\t.txmode = {\n+\t\t.mq_mode = ETH_MQ_TX_NONE,\n+\t},\n+};\n+\n+static uint32_t l2reflect_q;\n+static uint64_t l2reflect_break_usec = UINT64_MAX;\n+static uint64_t l2reflect_break_usec;\n+\n+static struct rte_ether_addr ether_bcast_addr = {\n+\t.addr_bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }\n+};\n+\n+struct rte_mempool *l2reflect_pktmbuf_pool;\n+\n+static void\n+l2reflect_usage(const char *prgname)\n+{\n+\tprintf(\"%s [EAL options] -- [-p PORT] -P [PRIO] [-b USEC] [-r] [-f] [-l]\"\n+\t       \"[-s] [-q] [-d] [-S] [-i MSEC]\\n\"\n+\t       \"  -p PORT: port to configure\\n\"\n+\t       \"  -P PRIO: scheduling priority to use\\n\"\n+\t       \"  -b USEC: break when latency > USEC\\n\"\n+\t       \"  -r: scheduling policy SCHED_RR\\n\"\n+\t       \"  -f: scheduling policy SCHED_FIFO\\n\"\n+\t       \"  -l: lock memory (mlockall)\\n\"\n+\t       \"  -s: send one packet on startup\\n\"\n+\t       \"  -q: quiet, do not print stats\\n\"\n+\t       \"  -d: die die die cli\\n\"\n+\t       \"  -H USEC: create histogram of latencies with USEC time slices\\n\"\n+\t       \"  -F FILE: write histogram to file\\n\"\n+\t       \"  -S: start processing threads in sleep, wake with SIGUSR2\\n\"\n+\t       \"  -i MSEC: use interrupts instead of polling (cont. on interrupt or after MSEC)\\n\",\n+\t       prgname);\n+}\n+\n+/* Parse the argument given in the command line of the application */\n+static int\n+l2reflect_parse_args(int argc, char **argv)\n+{\n+\tint opt, ret;\n+\tchar **argvopt;\n+\tint option_index;\n+\tchar *prgname = argv[0];\n+\tstatic struct option lgopts[] = { { NULL, 0, 0, 0 } };\n+\n+\targvopt = argv;\n+\tpolicy = SCHED_OTHER;\n+\thist_filename = NULL;\n+\tl2reflect_output_hist = 0;\n+\n+\twhile ((opt = getopt_long(argc, argvopt, \"p:P:b:H:F:i:sqdrflS\", lgopts,\n+\t\t\t\t  &option_index)) != EOF) {\n+\t\tswitch (opt) {\n+\t\t/* port */\n+\t\tcase 'p':\n+\t\t\tl2reflect_port_number =\n+\t\t\t\t(uint16_t)strtoul(optarg, NULL, 10);\n+\t\t\tbreak;\n+\t\tcase 'P':\n+\t\t\tpriority = strtoul(optarg, NULL, 10);\n+\t\t\tif (priority > 0) {\n+\t\t\t\tif (policy == SCHED_OTHER)\n+\t\t\t\t\tpolicy = SCHED_RR;\n+\t\t\t\tl2reflect_mlock = 1;\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase 'b':\n+\t\t\tl2reflect_break_usec = strtoul(optarg, NULL, 10);\n+\t\t\tbreak;\n+\t\tcase 'S':\n+\t\t\tsleep_start = 1;\n+\t\t\tbreak;\n+\t\tcase 'q':\n+\t\t\tquiet = 1;\n+\t\t\tbreak;\n+\t\tcase 'd':\n+\t\t\tdisable_int = 1;\n+\t\t\tbreak;\n+\t\tcase 'r':\n+\t\t\tpolicy = SCHED_RR;\n+\t\t\tbreak;\n+\t\tcase 'f':\n+\t\t\tpolicy = SCHED_FIFO;\n+\t\t\tbreak;\n+\t\tcase 'l':\n+\t\t\tl2reflect_mlock = 1;\n+\t\t\tbreak;\n+\t\tcase 'H':\n+\t\t\tl2reflect_hist = 1;\n+\t\t\thist_bucket_usec = strtoul(optarg, NULL, 10);\n+#ifndef RTE_HAS_CJSON\n+\t\t\tprintf(\"not compiled with cjson support\\n\");\n+\t\t\treturn -1;\n+#endif\n+\t\t\tbreak;\n+\t\tcase 'F':\n+\t\t\thist_filename = strndup(optarg, 128);\n+\t\t\tbreak;\n+\t\tcase 'i':\n+\t\t\tl2reflect_interrupt = 1;\n+\t\t\tl2reflect_sleep_msec = strtoul(optarg, NULL, 10);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tl2reflect_usage(prgname);\n+\t\t\treturn -1;\n+\t\t}\n+\t}\n+\n+\tif (optind >= 0)\n+\t\targv[optind - 1] = prgname;\n+\n+\tif (hist_filename && !l2reflect_hist) {\n+\t\tprintf(\"-F switch requires -H switch as well\\n\");\n+\t\treturn -1;\n+\t}\n+\n+\tret = optind - 1;\n+\toptind = 0; /* reset getopt lib */\n+\treturn ret;\n+}\n+\n+__rte_format_printf(1, 0)\n+static void\n+trace_write(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tchar buf[256];\n+\tint n, err;\n+\n+\tif (trace_fd == 0)\n+\t\ttrace_fd = open(\"/sys/kernel/debug/tracing/trace_marker\",\n+\t\t\t\tO_WRONLY);\n+\tif (trace_fd < 0)\n+\t\treturn;\n+\n+\tva_start(ap, fmt);\n+\tn = vsnprintf(buf, 256, fmt, ap);\n+\tva_end(ap);\n+\n+\terr = write(trace_fd, buf, n);\n+\tassert(err >= 1);\n+}\n+\n+/* Send a burst of one packet */\n+static int\n+l2reflect_send_packet(struct rte_mbuf **m, uint16_t port)\n+{\n+\tunsigned int ret;\n+\tstruct rte_ether_hdr *eth;\n+\tstruct my_magic_packet *pkt;\n+\tuint16_t type;\n+\n+\teth = rte_pktmbuf_mtod(*m, struct rte_ether_hdr *);\n+\tpkt = (struct my_magic_packet *)eth;\n+\ttype = pkt->type;\n+\n+\tret = rte_eth_tx_burst(port, l2reflect_q, m, 1);\n+\tif (unlikely(ret < 1)) {\n+\t\trte_pktmbuf_free(*m);\n+\t} else {\n+\t\tif (type == TRACE_TYPE_DATA) {\n+\t\t\tclock_gettime(CLOCK_MONOTONIC, &last_sent);\n+\t\t\tl2reflect_state = S_RUNNING;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static void\n+l2reflect_simple_forward(struct rte_mbuf *m)\n+{\n+\tstruct rte_ether_hdr *eth;\n+\tstruct my_magic_packet *pkt;\n+\n+\teth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);\n+\tpkt = (struct my_magic_packet *)eth;\n+\n+\t/* dst addr */\n+\trte_ether_addr_copy(&eth->s_addr, &eth->d_addr);\n+\t/* src addr */\n+\trte_ether_addr_copy(&l2reflect_port_eth_addr, &eth->s_addr);\n+\n+\tif ((eth->ether_type == rte_cpu_to_be_16(ETHER_TYPE_L2REFLECT)) &&\n+\t    (pkt->magic == MAGIC_TRACE_PAYLOAD)) {\n+\t\t/* and the native one */\n+\t\ttrace_write(\"sending traced packet\\n\");\n+\t}\n+\n+\tl2reflect_send_packet(&m, l2reflect_port_number);\n+}\n+\n+static struct rte_mbuf *\n+l2reflect_new_pkt(unsigned int type)\n+{\n+\tstruct rte_mbuf *m;\n+\tstruct rte_ether_hdr *eth;\n+\tstruct my_magic_packet *pkt;\n+\n+\tm = rte_pktmbuf_alloc(l2reflect_pktmbuf_pool);\n+\tif (m == NULL)\n+\t\trte_exit(EXIT_FAILURE, \"rte_pktmbuf_alloc failed\\n\");\n+\n+\teth = rte_pktmbuf_mtod(m, struct rte_ether_hdr *);\n+\n+\tif (l2reflect_state != S_ELECT_LEADER)\n+\t\trte_ether_addr_copy(&l2reflect_remote_eth_addr, &eth->d_addr);\n+\telse\n+\t\trte_ether_addr_copy(&ether_bcast_addr, &eth->d_addr);\n+\n+\t/* src addr */\n+\trte_ether_addr_copy(&l2reflect_port_eth_addr, &eth->s_addr);\n+\teth->ether_type = rte_cpu_to_be_16(ETHER_TYPE_L2REFLECT);\n+\n+\tm->data_len = 64;\n+\tm->nb_segs = 1;\n+\tm->pkt_len = 64;\n+\tm->l2_len = sizeof(struct rte_ether_hdr);\n+\n+\tpkt = (struct my_magic_packet *)eth;\n+\tpkt->type = type;\n+\tpkt->breakval = l2reflect_break_usec;\n+\n+\treturn m;\n+}\n+\n+static void\n+l2reflect_send_reset(void)\n+{\n+\tstruct rte_mbuf *m;\n+\tm = l2reflect_new_pkt(TRACE_TYPE_RSET);\n+\tl2reflect_send_packet(&m, l2reflect_port_number);\n+}\n+\n+static void\n+l2reflect_send_quit(void)\n+{\n+\tstruct rte_mbuf *m;\n+\tm = l2reflect_new_pkt(TRACE_TYPE_QUIT);\n+\tl2reflect_send_packet(&m, l2reflect_port_number);\n+}\n+\n+static void\n+l2reflect_new_ball(void)\n+{\n+\tstruct rte_mbuf *pnewball;\n+\tstruct rte_ether_hdr *eth;\n+\tstruct my_magic_packet *pkt;\n+\n+\tprintf(\"Should create a packet to play with ...\\n\");\n+\tpnewball = l2reflect_new_pkt(TRACE_TYPE_DATA);\n+\n+\teth = rte_pktmbuf_mtod(pnewball, struct rte_ether_hdr *);\n+\n+\tprintf(\"from MAC address: %02X:%02X:%02X:%02X:%02X:%02X to\"\n+\t\t\t\" %02X:%02X:%02X:%02X:%02X:%02X\\n\\n\",\n+\t\t\teth->s_addr.addr_bytes[0], eth->s_addr.addr_bytes[1],\n+\t\t\teth->s_addr.addr_bytes[2], eth->s_addr.addr_bytes[3],\n+\t\t\teth->s_addr.addr_bytes[4], eth->s_addr.addr_bytes[5],\n+\t\t\teth->d_addr.addr_bytes[0], eth->d_addr.addr_bytes[1],\n+\t\t\teth->d_addr.addr_bytes[2], eth->d_addr.addr_bytes[3],\n+\t\t\teth->d_addr.addr_bytes[4], eth->d_addr.addr_bytes[5]);\n+\n+\tpkt = (struct my_magic_packet *)eth;\n+\n+\t/* we are tracing lets tell the others */\n+\tif (l2reflect_break_usec)\n+\t\tpkt->magic = MAGIC_TRACE_PAYLOAD;\n+\n+\tl2reflect_send_packet(&pnewball, l2reflect_port_number);\n+}\n+\n+static inline int64_t\n+calcdiff_ns(struct timespec t1, struct timespec t2)\n+{\n+\tint64_t diff;\n+\tdiff = NSEC_PER_SEC * (int64_t)((int)t1.tv_sec - (int)t2.tv_sec);\n+\tdiff += ((int)t1.tv_nsec - (int)t2.tv_nsec);\n+\treturn diff;\n+}\n+\n+static inline unsigned int\n+l2reflect_rx_filter(\n+\tstruct rte_mbuf **bufs,\n+\tunsigned int nb_rx,\n+\tunsigned int data_only)\n+{\n+\tstruct rte_ether_hdr *eth;\n+\tstruct my_magic_packet *pkt;\n+\tunsigned int i, ret;\n+\n+\tret = 0;\n+\tfor (i = 0; i < nb_rx; i++) {\n+\t\teth = rte_pktmbuf_mtod(bufs[i], struct rte_ether_hdr *);\n+\t\tif (l2reflect_state != S_ELECT_LEADER &&\n+\t\t    !ether_addr_cmp(&eth->s_addr, &l2reflect_remote_eth_addr))\n+\t\t\tgoto drop;\n+\n+\t\tif (eth->ether_type != rte_cpu_to_be_16(ETHER_TYPE_L2REFLECT))\n+\t\t\tgoto drop;\n+\n+\t\tpkt = (struct my_magic_packet *)eth;\n+\t\tif (data_only && (pkt->type != TRACE_TYPE_DATA &&\n+\t\t\t\t  pkt->type != TRACE_TYPE_RSET &&\n+\t\t\t\t  pkt->type != TRACE_TYPE_QUIT))\n+\t\t\tgoto drop;\n+\n+\t\tbufs[ret++] = bufs[i];\n+\t\tcontinue;\n+drop:\n+\t\trte_pktmbuf_free(bufs[i]);\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int\n+elect_leader(uint16_t portid)\n+{\n+\tstruct rte_mbuf *pkts_burst[MAX_PKT_BURST];\n+\tstruct rte_mbuf *m;\n+\tstruct rte_ether_hdr *eth;\n+\tstruct my_magic_packet *pkt;\n+\tuint64_t my_mac, other_mac, breakval;\n+\tunsigned int i, nb_rx;\n+\tint i_win;\n+\tint searching = 1;\n+\n+\tother_mac = 0ULL;\n+\tmy_mac = 0ULL;\n+\tbreakval = 0;\n+\n+\twhile (l2reflect_state == S_ELECT_LEADER) {\n+\t\tnb_rx = rte_eth_rx_burst(portid, l2reflect_q, pkts_burst,\n+\t\t\t\t\t MAX_PKT_BURST);\n+\t\tnb_rx = l2reflect_rx_filter(pkts_burst, nb_rx, 0);\n+\t\tfor (i = 0; i < nb_rx && searching; i++) {\n+\t\t\teth = rte_pktmbuf_mtod(pkts_burst[i],\n+\t\t\t\t\t       struct rte_ether_hdr *);\n+\t\t\tpkt = (struct my_magic_packet *)eth;\n+\t\t\tif (pkt->type == TRACE_TYPE_HELO ||\n+\t\t\t    pkt->type == TRACE_TYPE_EHLO) {\n+\t\t\t\tl2reflect_state = S_RUNNING;\n+\t\t\t\trte_ether_addr_copy(&eth->s_addr,\n+\t\t\t\t\t\t    &l2reflect_remote_eth_addr);\n+\t\t\t\trte_ether_addr_copy(\n+\t\t\t\t\t&eth->s_addr,\n+\t\t\t\t\t(struct rte_ether_addr *)&other_mac);\n+\t\t\t\trte_ether_addr_copy(\n+\t\t\t\t\t&l2reflect_port_eth_addr,\n+\t\t\t\t\t(struct rte_ether_addr *)&my_mac);\n+\t\t\t\tbreakval = pkt->breakval;\n+\t\t\t\t/* break, but cleanup */\n+\t\t\t\tsearching = 0;\n+\t\t\t}\n+\t\t\tif (pkt->type == TRACE_TYPE_HELO) {\n+\t\t\t\tm = l2reflect_new_pkt(TRACE_TYPE_EHLO);\n+\t\t\t\tRTE_LOG(INFO, L2REFLECT, \"found one EHLO\\n\");\n+\t\t\t\tl2reflect_send_packet(&m,\n+\t\t\t\t\t\t      l2reflect_port_number);\n+\t\t\t}\n+\t\t\trte_pktmbuf_free(pkts_burst[i]);\n+\t\t}\n+\t\tm = l2reflect_new_pkt(TRACE_TYPE_HELO);\n+\t\tRTE_LOG(INFO, L2REFLECT, \"looking for player HELO\\n\");\n+\t\tl2reflect_send_packet(&m, l2reflect_port_number);\n+\t\tusleep(500000);\n+\t}\n+\t/* leave election logic */\n+\tif (l2reflect_state != S_RUNNING)\n+\t\treturn 0;\n+\n+\tif (my_mac == other_mac)\n+\t\trte_exit(EXIT_FAILURE, \"talking to myself ... confused\\n\");\n+\n+\t/* the one with the bigger MAC is the leader */\n+\ti_win = (my_mac > other_mac);\n+\n+\tRTE_LOG(INFO, L2REFLECT, \"i am the \\\"%s\\\"\\n\", i_win ? \"rick\" : \"morty\");\n+\n+\t/* looser takes tracing break value from winner */\n+\tif (!i_win)\n+\t\tl2reflect_break_usec = breakval;\n+\n+\treturn i_win;\n+}\n+\n+/*\n+ * add the measured time diff to the statistics.\n+ * return false if threshold is hit\n+ */\n+static inline bool\n+add_to_record(const uint64_t diff)\n+{\n+\trecord.rounds++;\n+\t/* do not count the first rounds, diff would be too high */\n+\tif (record.rounds > WARMUP_ROUNDS) {\n+\t\tif (l2reflect_hist) {\n+\t\t\tconst uint64_t bucket =\n+\t\t\t\tMIN(diff / (hist_bucket_usec * 1000),\n+\t\t\t\t    HIST_CAP_BUCKET);\n+\t\t\trecord.hist[bucket]++;\n+\t\t}\n+\n+\t\trecord.avg_round += (double)diff;\n+\t\tif (diff < record.min_round)\n+\t\t\trecord.min_round = diff;\n+\t\tif (diff > record.max_round) {\n+\t\t\trecord.max_round = diff;\n+\t\t\tif (l2reflect_break_usec &&\n+\t\t\t\t(record.max_round >\n+\t\t\t\t\tl2reflect_break_usec *\n+\t\t\t\t\t\t1000))\n+\t\t\t\treturn false;\n+\t\t}\n+\t}\n+\treturn true;\n+}\n+\n+/*\n+ * process a single packet.\n+ * return false if latency threshold is hit\n+ */\n+static inline bool\n+process_packet(\n+\tstruct my_magic_packet *pkt,\n+\tuint64_t *diff)\n+{\n+\tif (pkt->type == TRACE_TYPE_DATA) {\n+\t\tclock_gettime(CLOCK_MONOTONIC, &last_recv);\n+\t\t*diff = calcdiff_ns(last_recv, last_sent);\n+\t\tif (!add_to_record(*diff))\n+\t\t\treturn false;\n+\t}\n+\tif (pkt->magic == MAGIC_TRACE_PAYLOAD) {\n+\t\t/* and the native one */\n+\t\ttrace_write(\"received traced packet\\n\");\n+\t}\n+\treturn true;\n+}\n+\n+/* main processing loop */\n+static void\n+l2reflect_main_loop(void)\n+{\n+\tstruct rte_mbuf *pkts_burst[MAX_PKT_BURST];\n+\tstruct rte_mbuf *m;\n+\tunsigned int lcore_id;\n+\tunsigned int j, nb_rx, nb_evt;\n+\tuint16_t portid;\n+\tuint64_t diff = 0;\n+\tint sender;\n+\tstruct my_magic_packet *pkt;\n+\tstruct rte_ether_hdr *eth;\n+\tstruct rte_epoll_event event;\n+\n+\tlcore_id = rte_lcore_id();\n+\n+\tRTE_LOG(INFO, L2REFLECT, \"entering main loop on lcore %u\\n\", lcore_id);\n+\n+\tportid = l2reflect_port_number;\n+\tRTE_LOG(INFO, L2REFLECT, \" -- lcoreid=%u portid=%u\\n\", lcore_id,\n+\t\tportid);\n+\n+restart:\n+\tinit_record();\n+\tl2reflect_state = S_ELECT_LEADER;\n+\tsender = elect_leader(portid);\n+\n+\t/* we are the sender so we bring one ball into the game */\n+\tif (sender)\n+\t\tl2reflect_new_ball();\n+\n+\twhile (l2reflect_state == S_RUNNING) {\n+\t\tif (l2reflect_interrupt) {\n+\t\t\trte_eth_dev_rx_intr_enable(portid, l2reflect_q);\n+\t\t\t/* wait for interrupt or timeout */\n+\t\t\tnb_evt = rte_epoll_wait(RTE_EPOLL_PER_THREAD, &event, 1,\n+\t\t\t\t\t\tl2reflect_sleep_msec);\n+\t\t\trte_eth_dev_rx_intr_disable(portid, l2reflect_q);\n+\t\t\tif (nb_evt == 0 && rounds > WARMUP_ROUNDS)\n+\t\t\t\t++record.timeouts;\n+\t\t}\n+\n+\t\tnb_rx = rte_eth_rx_burst(portid, l2reflect_q, pkts_burst,\n+\t\t\t\t\t MAX_PKT_BURST);\n+\n+\t\t/* TODO use drivers/hw to filter mac */\n+\t\tnb_rx = l2reflect_rx_filter(pkts_burst, nb_rx, 1);\n+\n+\t\t/* remote is telling us to reset or stop */\n+\t\tif (nb_rx) {\n+\t\t\teth = rte_pktmbuf_mtod(pkts_burst[0],\n+\t\t\t\t\t       struct rte_ether_hdr *);\n+\t\t\tpkt = (struct my_magic_packet *)eth;\n+\t\t\tif (eth->ether_type ==\n+\t\t\t\trte_cpu_to_be_16(ETHER_TYPE_L2REFLECT)) {\n+\t\t\t\tif (pkt->type == TRACE_TYPE_RSET) {\n+\t\t\t\t\trte_pktmbuf_free(pkts_burst[0]);\n+\t\t\t\t\tgoto restart;\n+\t\t\t\t}\n+\t\t\t\tif (pkt->type == TRACE_TYPE_QUIT) {\n+\t\t\t\t\tl2reflect_state = S_REMOTE_TERM;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (l2reflect_state == S_RUNNING && nb_rx) {\n+\t\t\teth = rte_pktmbuf_mtod(pkts_burst[0],\n+\t\t\t\t\tstruct rte_ether_hdr *);\n+\t\t\tpkt = (struct my_magic_packet *)eth;\n+\t\t\tif (eth->ether_type ==\n+\t\t\t\trte_cpu_to_be_16(ETHER_TYPE_L2REFLECT)) {\n+\t\t\t\tif (!process_packet(pkt, &diff))\n+\t\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tfor (j = 0; j < nb_rx; j++) {\n+\t\t\tm = pkts_burst[j];\n+\t\t\trte_prefetch0(rte_pktmbuf_mtod(m, void *));\n+\t\t\tl2reflect_simple_forward(m);\n+\t\t}\n+\t}\n+\tconst int state_cpy = l2reflect_state;\n+\tswitch (state_cpy) {\n+\tcase S_RESET_TRX:\n+\t\tl2reflect_send_reset();\n+\t\tl2reflect_state = S_ELECT_LEADER;\n+\tcase S_ELECT_LEADER:\n+\t\tgoto restart;\n+\t}\n+\n+\tif (state_cpy == S_LOCAL_TERM) {\n+\t\tl2reflect_send_quit();\n+\t\tif (record.max_round > l2reflect_break_usec) {\n+\t\t\tprintf(\"hit latency threshold (%\" PRIu64\n+\t\t\t       \".%03u > %\" PRIu64 \")\\n\",\n+\t\t\t       diff / 1000, (unsigned int)(diff % 1000),\n+\t\t\t       l2reflect_break_usec);\n+\t\t}\n+\t} else if (state_cpy == S_REMOTE_TERM) {\n+\t\tprintf(\"received message that remote hit threshold (or is cancelled)\\n\");\n+\t}\n+}\n+\n+static int\n+l2reflect_launch_one_lcore(__rte_unused void *dummy)\n+{\n+\tstruct sched_param param;\n+\tint err;\n+\n+\tif (sleep_start) {\n+\t\tprintf(\"Sleeping and waiting for SIGCONT\\n\");\n+\t\twhile (sleep_start)\n+\t\t\tusleep(10000);\n+\n+\t\tprintf(\"Got SIGCONT, continuing\");\n+\t}\n+\tif (l2reflect_mlock) {\n+\t\terr = mlockall(MCL_CURRENT | MCL_FUTURE);\n+\t\tif (err)\n+\t\t\trte_exit(EXIT_FAILURE, \"mlockall failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t}\n+\tif (priority > 0 || policy != SCHED_OTHER) {\n+\t\tmemset(&param, 0, sizeof(param));\n+\t\tparam.sched_priority = priority;\n+\t\terr = sched_setscheduler(0, policy, &param);\n+\t\tif (err)\n+\t\t\trte_exit(EXIT_FAILURE,\n+\t\t\t\t \"sched_setscheduler failed: %s\\n\",\n+\t\t\t\t strerror(errno));\n+\t}\n+\tif (l2reflect_interrupt) {\n+\t\terr = rte_eth_dev_rx_intr_ctl_q(l2reflect_port_number,\n+\t\t\t\t\t\tl2reflect_q,\n+\t\t\t\t\t\tRTE_EPOLL_PER_THREAD,\n+\t\t\t\t\t\tRTE_INTR_EVENT_ADD, NULL);\n+\t\tif (err)\n+\t\t\trte_exit(EXIT_FAILURE,\n+\t\t\t\t \"could not register I/O interrupt\\n\");\n+\t}\n+\tl2reflect_main_loop();\n+\treturn 0;\n+}\n+\n+static void\n+sig_handler(int signum)\n+{\n+\tswitch (signum) {\n+\tcase SIGUSR1:\n+\t\tif (l2reflect_state == S_RUNNING)\n+\t\t\tl2reflect_state = S_RESET_TRX;\n+\t\tbreak;\n+\tcase SIGUSR2:\n+\t\tl2reflect_output_hist = 1;\n+\t\tbreak;\n+\tcase SIGCONT:\n+\t\tsleep_start = 0;\n+\t\tbreak;\n+\tcase SIGHUP:\n+\tcase SIGINT:\n+\t\tl2reflect_state = S_LOCAL_TERM;\n+\t\tbreak;\n+\t}\n+}\n+\n+int\n+main(int argc, char **argv)\n+{\n+\tstruct rte_eth_dev_info dev_info;\n+\tint ret;\n+\tuint32_t i;\n+\tuint16_t nb_ports;\n+\tunsigned int lcore_id;\n+\tstruct sigaction action;\n+\tbzero(&action, sizeof(action));\n+\tchar mempool_name[128];\n+\n+\taction.sa_handler = sig_handler;\n+\tif (sigaction(SIGHUP, &action, NULL) < 0 ||\n+\t    sigaction(SIGUSR1, &action, NULL) < 0 ||\n+\t    sigaction(SIGUSR2, &action, NULL) < 0 ||\n+\t    sigaction(SIGCONT, &action, NULL) < 0 ||\n+\t    sigaction(SIGINT, &action, NULL) < 0) {\n+\t\trte_exit(EXIT_FAILURE, \"Could not register signal handler\\n\");\n+\t}\n+\n+\tlcore_id = rte_lcore_id();\n+\t/* init EAL */\n+\tret = rte_eal_init(argc, argv);\n+\tif (ret < 0)\n+\t\trte_exit(EXIT_FAILURE, \"Invalid EAL arguments\\n\");\n+\targc -= ret;\n+\targv += ret;\n+\n+\t/* parse application arguments (after the EAL ones) */\n+\tret = l2reflect_parse_args(argc, argv);\n+\tif (ret < 0)\n+\t\trte_exit(EXIT_FAILURE, \"Invalid L2REFLECT arguments\\n\");\n+\n+\tsnprintf(mempool_name, sizeof(mempool_name), \"mbuf_pool_%d\", getpid());\n+\tprintf(\"About to create mempool \\\"%s\\\"\\n\", mempool_name);\n+\t/* create the mbuf pool */\n+\tl2reflect_pktmbuf_pool =\n+\t\trte_mempool_create(mempool_name, NB_MBUF, MBUF_SIZE,\n+\t\t\tMAX_PKT_BURST, sizeof(struct rte_pktmbuf_pool_private),\n+\t\t\trte_pktmbuf_pool_init, NULL,\n+\t\t\trte_pktmbuf_init, NULL, rte_socket_id(), 0);\n+\n+\tif (l2reflect_pktmbuf_pool == NULL)\n+\t\trte_exit(EXIT_FAILURE,\n+\t\t\t \"Cannot init/find mbuf pool name %s\\nError: %d %s\\n\",\n+\t\t\t mempool_name, rte_errno, rte_strerror(rte_errno));\n+\n+\tnb_ports = rte_eth_dev_count_avail();\n+\tif (nb_ports == 0)\n+\t\trte_exit(EXIT_FAILURE, \"No Ethernet ports - bye\\n\");\n+\tif (l2reflect_port_number + 1 > nb_ports)\n+\t\trte_exit(EXIT_FAILURE, \"Chosen port %d does not exist - bye\\n\",\n+\t\t\t l2reflect_port_number);\n+\tprintf(\"We have %d ports and will use port %d\\n\", nb_ports,\n+\t       l2reflect_port_number);\n+\n+\trte_eth_dev_info_get(l2reflect_port_number, &dev_info);\n+\tprintf(\"Initializing port %u... \", l2reflect_port_number);\n+\tfflush(stdout);\n+\n+\tif (l2reflect_interrupt)\n+\t\tport_conf.intr_conf.rxq = 1;\n+\tret = rte_eth_dev_configure(l2reflect_port_number, 1, 1, &port_conf);\n+\tif (ret < 0)\n+\t\trte_exit(EXIT_FAILURE,\n+\t\t\t \"Cannot configure device: err=%s, port=%u\\n\",\n+\t\t\t strerror(-ret), l2reflect_port_number);\n+\n+\trte_eth_macaddr_get(l2reflect_port_number, &l2reflect_port_eth_addr);\n+\n+\t/* init RX queues */\n+\tfflush(stdout);\n+\tfor (i = 0; i <= l2reflect_q; i++) {\n+\t\tret = rte_eth_rx_queue_setup(\n+\t\t\tl2reflect_port_number, i, nb_rxd,\n+\t\t\trte_eth_dev_socket_id(l2reflect_port_number), NULL,\n+\t\t\tl2reflect_pktmbuf_pool);\n+\t\tif (ret < 0)\n+\t\t\trte_exit(\n+\t\t\t\tEXIT_FAILURE,\n+\t\t\t\t\"rte_eth_rx_queue_setup:err=%s, port=%u q=%u\\n\",\n+\t\t\t\tstrerror(-ret), l2reflect_port_number, i);\n+\t}\n+\n+\t/* init one TX queue on each port */\n+\tfflush(stdout);\n+\tret = rte_eth_tx_queue_setup(\n+\t\tl2reflect_port_number, 0, nb_txd,\n+\t\trte_eth_dev_socket_id(l2reflect_port_number), NULL);\n+\tif (ret < 0)\n+\t\trte_exit(EXIT_FAILURE,\n+\t\t\t \"rte_eth_tx_queue_setup:err=%s, port=%u\\n\",\n+\t\t\t strerror(-ret), (unsigned int)l2reflect_port_number);\n+\n+\t/* Start device */\n+\tret = rte_eth_dev_start(l2reflect_port_number);\n+\tif (ret < 0)\n+\t\trte_exit(EXIT_FAILURE, \"rte_eth_dev_start:err=%s, port=%u\\n\",\n+\t\t\t strerror(-ret), (unsigned int)l2reflect_port_number);\n+\n+\trte_eth_promiscuous_enable(l2reflect_port_number);\n+\n+\tprintf(\"Port %u, MAC address: %02X:%02X:%02X:%02X:%02X:%02X\\n\\n\",\n+\t       (unsigned int)l2reflect_port_number,\n+\t       l2reflect_port_eth_addr.addr_bytes[0],\n+\t       l2reflect_port_eth_addr.addr_bytes[1],\n+\t       l2reflect_port_eth_addr.addr_bytes[2],\n+\t       l2reflect_port_eth_addr.addr_bytes[3],\n+\t       l2reflect_port_eth_addr.addr_bytes[4],\n+\t       l2reflect_port_eth_addr.addr_bytes[5]);\n+\n+\t/*\n+\t * in quiet mode the master executes the main packet loop\n+\t * otherwise the one slave does it and the master prints stats\n+\t */\n+\tif (quiet) {\n+\t\tassert(rte_lcore_count() == 1);\n+\t\tif (disable_int) {\n+\t\t\tiopl(3);\n+\t\t\tasm(\"cli\");\n+\t\t}\n+\t\tl2reflect_launch_one_lcore(NULL);\n+\t} else {\n+\t\tassert(rte_lcore_count() == 2);\n+\t\t/* the slave reflects the packets */\n+\t\tRTE_LCORE_FOREACH_SLAVE(lcore_id)\n+\t\t{\n+\t\t\trte_eal_remote_launch(l2reflect_launch_one_lcore, NULL,\n+\t\t\t\t\t      lcore_id);\n+\t\t}\n+\n+\t\t/* the master prints the stats */\n+\t\tinit_record();\n+\t\tl2reflect_stats_loop();\n+\t\trte_eal_mp_wait_lcore();\n+\t}\n+\trte_eal_cleanup();\n+\n+\tif (l2reflect_hist)\n+\t\toutput_histogram_snapshot();\n+\n+\tcleanup_record();\n+\n+\tif (trace_fd)\n+\t\tclose(trace_fd);\n+\n+\treturn 0;\n+}\ndiff --git a/app/l2reflect/meson.build b/app/l2reflect/meson.build\nnew file mode 100644\nindex 000000000..54638e114\n--- /dev/null\n+++ b/app/l2reflect/meson.build\n@@ -0,0 +1,21 @@\n+# SPDX-License-Identifier: BSD-3-Clause\n+# Copyright(c) 2020 Siemens AG\n+\n+# meson file, for building this example as part of a main DPDK build.\n+\n+cjson = dependency('libcjson', required: false)\n+\n+if not cjson.found()\n+    cc = meson.get_compiler('c')\n+    cjson = cc.find_library('cjson', required: false)\n+endif\n+\n+if not is_linux\n+        build = false\n+endif\n+\n+sources = files('main.c', 'stats.c')\n+if cjson.found()\n+    ext_deps += cjson\n+    cflags += '-DRTE_HAS_CJSON'\n+endif\ndiff --git a/app/l2reflect/stats.c b/app/l2reflect/stats.c\nnew file mode 100644\nindex 000000000..62ff44b27\n--- /dev/null\n+++ b/app/l2reflect/stats.c\n@@ -0,0 +1,201 @@\n+/*\n+ * SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2020 Siemens AG\n+ *\n+ * authors:\n+ *   Felix Moessbauer <felix.moessbauer@siemens.com>\n+ *   Henning Schild <henning.schild@siemens.com>\n+ */\n+#include <stdio.h>\n+#include <sys/types.h>\n+#include <unistd.h>\n+#include <string.h>\n+#ifdef RTE_HAS_CJSON\n+#include <cjson/cJSON.h>\n+#endif\n+#include \"stats.h\"\n+\n+#define ANSI_BOLD_RED \"\\x1b[01;31m\"\n+#define ANSI_BOLD_GREEN \"\\x1b[01;32m\"\n+#define ANSI_BOLD_YELLOW \"\\x1b[01;33m\"\n+#define ANSI_BOLD_BLUE \"\\x1b[01;34m\"\n+#define ANSI_BOLD_MAGENTA \"\\x1b[01;35m\"\n+#define ANSI_BOLD_CYAN \"\\x1b[01;36m\"\n+#define ANSI_RESET \"\\x1b[0m\"\n+\n+void\n+init_record(void)\n+{\n+\trecord.max_round = 0;\n+\trecord.min_round = MIN_INITIAL;\n+\trecord.rounds = 0;\n+\trecord.timeouts = 0;\n+\trecord.avg_round = 0;\n+\tif (l2reflect_hist) {\n+\t\tif (!record.hist_size) {\n+\t\t\trecord.hist =\n+\t\t\t\tcalloc(HIST_NUM_BUCKETS, sizeof(uint64_t));\n+\t\t\trecord.hist_size = HIST_NUM_BUCKETS;\n+\t\t} else {\n+\t\t\tmemset(record.hist, 0,\n+\t\t\t       record.hist_size * sizeof(uint64_t));\n+\t\t}\n+\t}\n+}\n+\n+void\n+cleanup_record(void)\n+{\n+\tif (l2reflect_hist) {\n+\t\tfree(record.hist);\n+\t\trecord.hist = NULL;\n+\t\trecord.hist_size = 0;\n+\t}\n+}\n+\n+void\n+output_histogram_snapshot(void)\n+{\n+\tchar *json = serialize_histogram(&record);\n+\tFILE *fd = stdout;\n+\tif (hist_filename)\n+\t\tfd = fopen(hist_filename, \"w\");\n+\tfputs(json, fd);\n+\tfputs(\"\\n\", fd);\n+\tfree(json);\n+\tif (hist_filename)\n+\t\tfclose(fd);\n+}\n+\n+void\n+print_stats(void)\n+{\n+\tconst char clr[] = { 27, '[', '2', 'J', '\\0' };\n+\tconst char topLeft[] = { 27, '[', '1', ';', '1', 'H', '\\0' };\n+\tconst uint64_t bytes_in_gib = 0x40000000;\n+\tstruct rte_eth_stats stats;\n+\n+\trte_eth_stats_get(l2reflect_port_number, &stats);\n+\n+\t/* Clear screen and move to top left */\n+\tprintf(\"%s%s\", clr, topLeft);\n+\n+\tprintf(ANSI_BOLD_GREEN \"Networking Roundtrip Test\\n\"\n+\t\t\tANSI_RESET);\n+\tprintf(ANSI_BOLD_MAGENTA\n+\t\t\t\"\\nPort statistics ====================================\"\n+\t\t\tANSI_RESET);\n+\n+\tprintf(\"\\nMe: %02X:%02X:%02X:%02X:%02X:%02X <--> \"\n+\t\t\t\"Remote: %02X:%02X:%02X:%02X:%02X:%02X\",\n+\t\t\tl2reflect_port_eth_addr.addr_bytes[0],\n+\t\t\tl2reflect_port_eth_addr.addr_bytes[1],\n+\t\t\tl2reflect_port_eth_addr.addr_bytes[2],\n+\t\t\tl2reflect_port_eth_addr.addr_bytes[3],\n+\t\t\tl2reflect_port_eth_addr.addr_bytes[4],\n+\t\t\tl2reflect_port_eth_addr.addr_bytes[5],\n+\t\t\tl2reflect_remote_eth_addr.addr_bytes[0],\n+\t\t\tl2reflect_remote_eth_addr.addr_bytes[1],\n+\t\t\tl2reflect_remote_eth_addr.addr_bytes[2],\n+\t\t\tl2reflect_remote_eth_addr.addr_bytes[3],\n+\t\t\tl2reflect_remote_eth_addr.addr_bytes[4],\n+\t\t\tl2reflect_remote_eth_addr.addr_bytes[5]);\n+\tprintf(\"\\nStatistics for port %d PID %d on lcore %d ---------\"\n+\t\t\t\"\\nPackets tx: %22\" PRIu64 \"\\nPackets rx: %22\" PRIu64\n+\t\t\t\"\\nBytes tx: %24\" PRIu64 \"    (%8.2f GiB)\"\n+\t\t\t\"\\nBytes rx: %24\" PRIu64 \"    (%8.2f GiB)\"\n+\t\t\t\"\\nErrors tx: %23\" PRIu64 \"\\nErrors rx: %23\" PRIu64\n+\t\t\t\"\\nTimeouts rx: %21\" PRIu64 \"    (>%9\" PRIu64 \"ms)\",\n+\t\t\tl2reflect_port_number, getpid(), rte_lcore_id(),\n+\t\t\tstats.opackets, stats.ipackets, stats.obytes,\n+\t\t\t(double)stats.obytes / bytes_in_gib, stats.ibytes,\n+\t\t\t(double)stats.ibytes / bytes_in_gib, stats.oerrors,\n+\t\t\tstats.ierrors, record.timeouts, l2reflect_sleep_msec);\n+\tprintf(ANSI_BOLD_MAGENTA\n+\t\t\t\"\\nPort timing statistics =============================\"\n+\t\t\tANSI_RESET);\n+\tif (l2reflect_state == S_ELECT_LEADER ||\n+\t\trecord.min_round == MIN_INITIAL) {\n+\t\tprintf(\"\\n\\nBenchmark not started yet...\\n\");\n+\t} else {\n+\t\tprintf(\"\\n\" ANSI_BOLD_RED \"Max\" ANSI_RESET\n+\t\t\t\t\" roundtrip: %19\" PRIu64 \" us\",\n+\t\t\t\trecord.max_round / 1000);\n+\t\tprintf(\"\\n\" ANSI_BOLD_YELLOW \"Avg\" ANSI_RESET\n+\t\t\t\t\" roundtrip: %19\" PRIu64 \" us\",\n+\t\t\t\trecord.rounds ? (uint64_t)(record.avg_round /\n+\t\t\t\trecord.rounds / 1000) :\n+\t\t\t\t0);\n+\t\tprintf(\"\\n\" ANSI_BOLD_GREEN \"Min\" ANSI_RESET\n+\t\t\t\t\" roundtrip: %19\" PRIu64 \" us\",\n+\t\t\t\trecord.min_round / 1000);\n+\t}\n+\tprintf(ANSI_BOLD_MAGENTA\n+\t\t\t\"\\n====================================================\"\n+\t\t\tANSI_RESET \"\\n\");\n+}\n+\n+void\n+l2reflect_stats_loop(void)\n+{\n+\twhile (!(l2reflect_state & (S_LOCAL_TERM | S_REMOTE_TERM))) {\n+\t\tprint_stats();\n+\t\tif (l2reflect_hist && l2reflect_output_hist) {\n+\t\t\toutput_histogram_snapshot();\n+\t\t\tl2reflect_output_hist = 0;\n+\t\t}\n+\t\tsleep(1);\n+\t}\n+}\n+\n+char *\n+serialize_histogram(__rte_unused const struct stats *record)\n+{\n+#ifndef RTE_HAS_CJSON\n+\treturn strdup(\"to print histogram, build with cjson support\");\n+#else\n+\tchar *str = NULL;\n+\tchar key[8];\n+\tunsigned int i;\n+\tcJSON *hist0, *cpu0, *all_cpus, *output;\n+\n+\toutput = cJSON_CreateObject();\n+\t/* version: 1 */\n+\tcJSON_AddItemToObject(output, \"version\", cJSON_CreateNumber(1));\n+\n+\t/* cpu 0 histogram */\n+\thist0 = cJSON_CreateObject();\n+\tfor (i = 0; i < record->hist_size; ++i) {\n+\t\t/* only log positive numbers to meet jitterplot format */\n+\t\tif (record->hist[i] != 0) {\n+\t\t\tsnprintf(key, 8, \"%u\", i * hist_bucket_usec);\n+\t\t\tcJSON_AddNumberToObject(hist0, key, record->hist[i]);\n+\t\t}\n+\t}\n+\n+\t/* cpu 0 stats */\n+\tcpu0 = cJSON_CreateObject();\n+\tcJSON_AddItemToObject(cpu0, \"histogram\", hist0);\n+\tcJSON_AddItemToObject(cpu0, \"count\",\n+\t\t\t\t\tcJSON_CreateNumber(record->rounds));\n+\tcJSON_AddItemToObject(cpu0, \"min\",\n+\t\t\t\t\tcJSON_CreateNumber(record->min_round));\n+\tcJSON_AddItemToObject(cpu0, \"max\",\n+\t\t\t\t\tcJSON_CreateNumber(record->max_round));\n+\tcJSON_AddItemToObject(cpu0, \"avg\",\n+\t\t\t\t\tcJSON_CreateNumber((record->avg_round /\n+\t\t\t\t\t\trecord->rounds / 1000)));\n+\n+\t/* combine objects */\n+\tall_cpus = cJSON_CreateObject();\n+\tcJSON_AddItemToObject(all_cpus, \"0\", cpu0);\n+\tcJSON_AddItemToObject(output, \"cpu\", all_cpus);\n+\n+\tstr = cJSON_Print(output);\n+\n+\t/* cleanup */\n+\tcJSON_Delete(output);\n+\n+\treturn str;\n+#endif\n+}\ndiff --git a/app/l2reflect/stats.h b/app/l2reflect/stats.h\nnew file mode 100644\nindex 000000000..4b3c11351\n--- /dev/null\n+++ b/app/l2reflect/stats.h\n@@ -0,0 +1,67 @@\n+/*\n+ * SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2020 Siemens AG\n+ *\n+ * authors:\n+ *   Felix Moessbauer <felix.moessbauer@siemens.com>\n+ *   Henning Schild <henning.schild@siemens.com>\n+ */\n+#ifndef _STATS_H_\n+#define _STATS_H_\n+#include <stdint.h>\n+#include <stdatomic.h>\n+#include <limits.h>\n+\n+#include <rte_ethdev.h>\n+\n+#include \"l2reflect.h\"\n+\n+#define MIN_INITIAL ULONG_MAX\n+#define HIST_NUM_BUCKETS 128\n+#define HIST_CAP_BUCKET (HIST_NUM_BUCKETS - 1)\n+\n+/* size of each histogram bucket in usec */\n+unsigned int hist_bucket_usec;\n+\n+/* runtime statistics */\n+struct stats {\n+\tuint64_t max_round;\n+\tuint64_t min_round;\n+\tuint64_t rounds;\n+\tuint64_t timeouts;\n+\tdouble avg_round;\n+\tunsigned int hist_size;\n+\t/* each slot is 10us */\n+\tuint64_t *hist;\n+};\n+\n+struct stats record;\n+char *hist_filename;\n+\n+void\n+init_record(void);\n+void\n+cleanup_record(void);\n+\n+void\n+l2reflect_stats_loop(void);\n+\n+/*\n+ * Write the histogram to file / stdio without any locking.\n+ * When called during the measurement, values are approximations\n+ * (racy reads).\n+ */\n+void\n+output_histogram_snapshot(void);\n+\n+/* Print out statistics on packets dropped */\n+void\n+print_stats(void);\n+\n+/*\n+ * get a JSON representation of the record\n+ */\n+char *\n+serialize_histogram(const struct stats *record);\n+\n+#endif /* _STATS_H_ */\ndiff --git a/app/meson.build b/app/meson.build\nindex a29eba024..3dfdbb58c 100644\n--- a/app/meson.build\n+++ b/app/meson.build\n@@ -8,6 +8,7 @@ endif\n apps = [\n \t'pdump',\n \t'proc-info',\n+\t'l2reflect',\n \t'test-acl',\n \t'test-bbdev',\n \t'test-cmdline',\n",
    "prefixes": [
        "v2",
        "2/2"
    ]
}