1d30ea906Sjfb8856606..  SPDX-License-Identifier: BSD-3-Clause
2d30ea906Sjfb8856606    Copyright(c) 2016-2017 Intel Corporation.
32bfe3f2eSlogwang
42bfe3f2eSlogwangServer-Node EFD Sample Application
52bfe3f2eSlogwang==================================
62bfe3f2eSlogwang
72bfe3f2eSlogwangThis sample application demonstrates the use of EFD library as a flow-level
82bfe3f2eSlogwangload balancer, for more information about the EFD Library please refer to the
92bfe3f2eSlogwangDPDK programmer's guide.
102bfe3f2eSlogwang
112bfe3f2eSlogwangThis sample application is a variant of the
122bfe3f2eSlogwang:ref:`client-server sample application <multi_process_app>`
132bfe3f2eSlogwangwhere a specific target node is specified for every and each flow
142bfe3f2eSlogwang(not in a round-robin fashion as the original load balancing sample application).
152bfe3f2eSlogwang
162bfe3f2eSlogwangOverview
172bfe3f2eSlogwang--------
182bfe3f2eSlogwang
192bfe3f2eSlogwangThe architecture of the EFD flow-based load balancer sample application is
202bfe3f2eSlogwangpresented in the following figure.
212bfe3f2eSlogwang
222bfe3f2eSlogwang.. _figure_efd_sample_app_overview:
232bfe3f2eSlogwang
242bfe3f2eSlogwang.. figure:: img/server_node_efd.*
252bfe3f2eSlogwang
262bfe3f2eSlogwang   Using EFD as a Flow-Level Load Balancer
272bfe3f2eSlogwang
282bfe3f2eSlogwangAs shown in :numref:`figure_efd_sample_app_overview`,
292bfe3f2eSlogwangthe sample application consists of a front-end node (server)
302bfe3f2eSlogwangusing the EFD library to create a load-balancing table for flows,
312bfe3f2eSlogwangfor each flow a target backend worker node is specified. The EFD table does not
322bfe3f2eSlogwangstore the flow key (unlike a regular hash table), and hence, it can
332bfe3f2eSlogwangindividually load-balance millions of flows (number of targets * maximum number
342bfe3f2eSlogwangof flows fit in a flow table per target) while still fitting in CPU cache.
352bfe3f2eSlogwang
362bfe3f2eSlogwangIt should be noted that although they are referred to as nodes, the frontend
372bfe3f2eSlogwangserver and worker nodes are processes running on the same platform.
382bfe3f2eSlogwang
392bfe3f2eSlogwangFront-end Server
402bfe3f2eSlogwang~~~~~~~~~~~~~~~~
412bfe3f2eSlogwang
422bfe3f2eSlogwangUpon initializing, the frontend server node (process) creates a flow
432bfe3f2eSlogwangdistributor table (based on the EFD library) which is populated with flow
442bfe3f2eSlogwanginformation and its intended target node.
452bfe3f2eSlogwang
462bfe3f2eSlogwangThe sample application assigns a specific target node_id (process) for each of
472bfe3f2eSlogwangthe IP destination addresses as follows:
482bfe3f2eSlogwang
492bfe3f2eSlogwang.. code-block:: c
502bfe3f2eSlogwang
512bfe3f2eSlogwang    node_id = i % num_nodes; /* Target node id is generated */
522bfe3f2eSlogwang    ip_dst = rte_cpu_to_be_32(i); /* Specific ip destination address is
532bfe3f2eSlogwang                                     assigned to this target node */
542bfe3f2eSlogwang
552bfe3f2eSlogwangthen the pair of <key,target> is inserted into the flow distribution table.
562bfe3f2eSlogwang
572bfe3f2eSlogwangThe main loop of the server process receives a burst of packets, then for
582bfe3f2eSlogwangeach packet, a flow key (IP destination address) is extracted. The flow
592bfe3f2eSlogwangdistributor table is looked up and the target node id is returned.  Packets are
602bfe3f2eSlogwangthen enqueued to the specified target node id.
612bfe3f2eSlogwang
622bfe3f2eSlogwangIt should be noted that flow distributor table is not a membership test table.
632bfe3f2eSlogwangI.e. if the key has already been inserted the target node id will be correct,
642bfe3f2eSlogwangbut for new keys the flow distributor table will return a value (which can be
652bfe3f2eSlogwangvalid).
662bfe3f2eSlogwang
672bfe3f2eSlogwangBackend Worker Nodes
682bfe3f2eSlogwang~~~~~~~~~~~~~~~~~~~~
692bfe3f2eSlogwang
702bfe3f2eSlogwangUpon initializing, the worker node (process) creates a flow table (a regular
712bfe3f2eSlogwanghash table that stores the key default size 1M flows) which is populated with
722bfe3f2eSlogwangonly the flow information that is serviced at this node. This flow key is
732bfe3f2eSlogwangessential to point out new keys that have not been inserted before.
742bfe3f2eSlogwang
752bfe3f2eSlogwangThe worker node's main loop is simply receiving packets then doing a hash table
762bfe3f2eSlogwanglookup. If a match occurs then statistics are updated for flows serviced by
772bfe3f2eSlogwangthis node. If no match is found in the local hash table then this indicates
782bfe3f2eSlogwangthat this is a new flow, which is dropped.
792bfe3f2eSlogwang
802bfe3f2eSlogwang
812bfe3f2eSlogwangCompiling the Application
822bfe3f2eSlogwang-------------------------
832bfe3f2eSlogwang
842bfe3f2eSlogwangTo compile the sample application see :doc:`compiling`.
852bfe3f2eSlogwang
862bfe3f2eSlogwangThe application is located in the ``server_node_efd`` sub-directory.
872bfe3f2eSlogwang
882bfe3f2eSlogwangRunning the Application
892bfe3f2eSlogwang-----------------------
902bfe3f2eSlogwang
912bfe3f2eSlogwangThe application has two binaries to be run: the front-end server
922bfe3f2eSlogwangand the back-end node.
932bfe3f2eSlogwang
942bfe3f2eSlogwangThe frontend server (server) has the following command line options::
952bfe3f2eSlogwang
96*2d9fd380Sjfb8856606    ./<build_dir>/examples/dpdk-server [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS
972bfe3f2eSlogwang
982bfe3f2eSlogwangWhere,
992bfe3f2eSlogwang
1002bfe3f2eSlogwang* ``-p PORTMASK:`` Hexadecimal bitmask of ports to configure
1012bfe3f2eSlogwang* ``-n NUM_NODES:`` Number of back-end nodes that will be used
1022bfe3f2eSlogwang* ``-f NUM_FLOWS:`` Number of flows to be added in the EFD table (1 million, by default)
1032bfe3f2eSlogwang
1042bfe3f2eSlogwangThe back-end node (node) has the following command line options::
1052bfe3f2eSlogwang
1062bfe3f2eSlogwang    ./node [EAL options] -- -n NODE_ID
1072bfe3f2eSlogwang
1082bfe3f2eSlogwangWhere,
1092bfe3f2eSlogwang
1102bfe3f2eSlogwang* ``-n NODE_ID:`` Node ID, which cannot be equal or higher than NUM_MODES
1112bfe3f2eSlogwang
1122bfe3f2eSlogwang
1132bfe3f2eSlogwangFirst, the server app must be launched, with the number of nodes that will be run.
1142bfe3f2eSlogwangOnce it has been started, the node instances can be run, with different NODE_ID.
1152bfe3f2eSlogwangThese instances have to be run as secondary processes, with ``--proc-type=secondary``
1162bfe3f2eSlogwangin the EAL options, which will attach to the primary process memory, and therefore,
1172bfe3f2eSlogwangthey can access the queues created by the primary process to distribute packets.
1182bfe3f2eSlogwang
1192bfe3f2eSlogwangTo successfully run the application, the command line used to start the
1202bfe3f2eSlogwangapplication has to be in sync with the traffic flows configured on the traffic
1212bfe3f2eSlogwanggenerator side.
1222bfe3f2eSlogwang
1232bfe3f2eSlogwangFor examples of application command lines and traffic generator flows, please
1242bfe3f2eSlogwangrefer to the DPDK Test Report. For more details on how to set up and run the
1252bfe3f2eSlogwangsample applications provided with DPDK package, please refer to the
1262bfe3f2eSlogwang:ref:`DPDK Getting Started Guide for Linux <linux_gsg>` and
1272bfe3f2eSlogwang:ref:`DPDK Getting Started Guide for FreeBSD <freebsd_gsg>`.
1282bfe3f2eSlogwang
1292bfe3f2eSlogwang
1302bfe3f2eSlogwangExplanation
1312bfe3f2eSlogwang-----------
1322bfe3f2eSlogwang
1332bfe3f2eSlogwangAs described in previous sections, there are two processes in this example.
1342bfe3f2eSlogwang
1352bfe3f2eSlogwangThe first process, the front-end server, creates and populates the EFD table,
1362bfe3f2eSlogwangwhich is used to distribute packets to nodes, which the number of flows
1372bfe3f2eSlogwangspecified in the command line (1 million, by default).
1382bfe3f2eSlogwang
1392bfe3f2eSlogwang
1402bfe3f2eSlogwang.. code-block:: c
1412bfe3f2eSlogwang
1422bfe3f2eSlogwang    static void
1432bfe3f2eSlogwang    create_efd_table(void)
1442bfe3f2eSlogwang    {
1452bfe3f2eSlogwang        uint8_t socket_id = rte_socket_id();
1462bfe3f2eSlogwang
1472bfe3f2eSlogwang        /* create table */
1482bfe3f2eSlogwang        efd_table = rte_efd_create("flow table", num_flows * 2, sizeof(uint32_t),
1492bfe3f2eSlogwang                        1 << socket_id, socket_id);
1502bfe3f2eSlogwang
1512bfe3f2eSlogwang        if (efd_table == NULL)
1522bfe3f2eSlogwang            rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
1532bfe3f2eSlogwang    }
1542bfe3f2eSlogwang
1552bfe3f2eSlogwang    static void
1562bfe3f2eSlogwang    populate_efd_table(void)
1572bfe3f2eSlogwang    {
1582bfe3f2eSlogwang        unsigned int i;
1592bfe3f2eSlogwang        int32_t ret;
1602bfe3f2eSlogwang        uint32_t ip_dst;
1612bfe3f2eSlogwang        uint8_t socket_id = rte_socket_id();
1622bfe3f2eSlogwang        uint64_t node_id;
1632bfe3f2eSlogwang
1642bfe3f2eSlogwang        /* Add flows in table */
1652bfe3f2eSlogwang        for (i = 0; i < num_flows; i++) {
1662bfe3f2eSlogwang            node_id = i % num_nodes;
1672bfe3f2eSlogwang
1682bfe3f2eSlogwang            ip_dst = rte_cpu_to_be_32(i);
1692bfe3f2eSlogwang            ret = rte_efd_update(efd_table, socket_id,
1702bfe3f2eSlogwang                            (void *)&ip_dst, (efd_value_t)node_id);
1712bfe3f2eSlogwang            if (ret < 0)
1722bfe3f2eSlogwang                rte_exit(EXIT_FAILURE, "Unable to add entry %u in "
1732bfe3f2eSlogwang                                    "EFD table\n", i);
1742bfe3f2eSlogwang        }
1752bfe3f2eSlogwang
1762bfe3f2eSlogwang        printf("EFD table: Adding 0x%x keys\n", num_flows);
1772bfe3f2eSlogwang    }
1782bfe3f2eSlogwang
1792bfe3f2eSlogwangAfter initialization, packets are received from the enabled ports, and the IPv4
1802bfe3f2eSlogwangaddress from the packets is used as a key to look up in the EFD table,
1812bfe3f2eSlogwangwhich tells the node where the packet has to be distributed.
1822bfe3f2eSlogwang
1832bfe3f2eSlogwang.. code-block:: c
1842bfe3f2eSlogwang
1852bfe3f2eSlogwang    static void
1862bfe3f2eSlogwang    process_packets(uint32_t port_num __rte_unused, struct rte_mbuf *pkts[],
1872bfe3f2eSlogwang            uint16_t rx_count, unsigned int socket_id)
1882bfe3f2eSlogwang    {
1892bfe3f2eSlogwang        uint16_t i;
1902bfe3f2eSlogwang        uint8_t node;
1912bfe3f2eSlogwang        efd_value_t data[EFD_BURST_MAX];
1922bfe3f2eSlogwang        const void *key_ptrs[EFD_BURST_MAX];
1932bfe3f2eSlogwang
1944418919fSjohnjiang        struct rte_ipv4_hdr *ipv4_hdr;
1952bfe3f2eSlogwang        uint32_t ipv4_dst_ip[EFD_BURST_MAX];
1962bfe3f2eSlogwang
1972bfe3f2eSlogwang        for (i = 0; i < rx_count; i++) {
1982bfe3f2eSlogwang            /* Handle IPv4 header.*/
1994418919fSjohnjiang            ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct rte_ipv4_hdr *,
2004418919fSjohnjiang                    sizeof(struct rte_ether_hdr));
2012bfe3f2eSlogwang            ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
2022bfe3f2eSlogwang            key_ptrs[i] = (void *)&ipv4_dst_ip[i];
2032bfe3f2eSlogwang        }
2042bfe3f2eSlogwang
2052bfe3f2eSlogwang        rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
2062bfe3f2eSlogwang                    (const void **) key_ptrs, data);
2072bfe3f2eSlogwang        for (i = 0; i < rx_count; i++) {
2082bfe3f2eSlogwang            node = (uint8_t) ((uintptr_t)data[i]);
2092bfe3f2eSlogwang
2102bfe3f2eSlogwang            if (node >= num_nodes) {
2112bfe3f2eSlogwang                /*
2122bfe3f2eSlogwang                 * Node is out of range, which means that
2132bfe3f2eSlogwang                 * flow has not been inserted
2142bfe3f2eSlogwang                 */
2152bfe3f2eSlogwang                flow_dist_stats.drop++;
2162bfe3f2eSlogwang                rte_pktmbuf_free(pkts[i]);
2172bfe3f2eSlogwang            } else {
2182bfe3f2eSlogwang                flow_dist_stats.distributed++;
2192bfe3f2eSlogwang                enqueue_rx_packet(node, pkts[i]);
2202bfe3f2eSlogwang            }
2212bfe3f2eSlogwang        }
2222bfe3f2eSlogwang
2232bfe3f2eSlogwang        for (i = 0; i < num_nodes; i++)
2242bfe3f2eSlogwang            flush_rx_queue(i);
2252bfe3f2eSlogwang    }
2262bfe3f2eSlogwang
2272bfe3f2eSlogwangThe burst of packets received is enqueued in temporary buffers (per node),
2282bfe3f2eSlogwangand enqueued in the shared ring between the server and the node.
2292bfe3f2eSlogwangAfter this, a new burst of packets is received and this process is
2302bfe3f2eSlogwangrepeated infinitely.
2312bfe3f2eSlogwang
2322bfe3f2eSlogwang.. code-block:: c
2332bfe3f2eSlogwang
2342bfe3f2eSlogwang    static void
2352bfe3f2eSlogwang    flush_rx_queue(uint16_t node)
2362bfe3f2eSlogwang    {
2372bfe3f2eSlogwang        uint16_t j;
2382bfe3f2eSlogwang        struct node *cl;
2392bfe3f2eSlogwang
2402bfe3f2eSlogwang        if (cl_rx_buf[node].count == 0)
2412bfe3f2eSlogwang            return;
2422bfe3f2eSlogwang
2432bfe3f2eSlogwang        cl = &nodes[node];
2442bfe3f2eSlogwang        if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
2452bfe3f2eSlogwang                cl_rx_buf[node].count, NULL) != cl_rx_buf[node].count){
2462bfe3f2eSlogwang            for (j = 0; j < cl_rx_buf[node].count; j++)
2472bfe3f2eSlogwang                rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
2482bfe3f2eSlogwang            cl->stats.rx_drop += cl_rx_buf[node].count;
2492bfe3f2eSlogwang        } else
2502bfe3f2eSlogwang            cl->stats.rx += cl_rx_buf[node].count;
2512bfe3f2eSlogwang
2522bfe3f2eSlogwang        cl_rx_buf[node].count = 0;
2532bfe3f2eSlogwang    }
2542bfe3f2eSlogwang
2552bfe3f2eSlogwangThe second process, the back-end node, receives the packets from the shared
2562bfe3f2eSlogwangring with the server and send them out, if they belong to the node.
2572bfe3f2eSlogwang
2582bfe3f2eSlogwangAt initialization, it attaches to the server process memory, to have
2592bfe3f2eSlogwangaccess to the shared ring, parameters and statistics.
2602bfe3f2eSlogwang
2612bfe3f2eSlogwang.. code-block:: c
2622bfe3f2eSlogwang
2632bfe3f2eSlogwang    rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
2642bfe3f2eSlogwang    if (rx_ring == NULL)
2652bfe3f2eSlogwang        rte_exit(EXIT_FAILURE, "Cannot get RX ring - "
2662bfe3f2eSlogwang                "is server process running?\n");
2672bfe3f2eSlogwang
2682bfe3f2eSlogwang    mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
2692bfe3f2eSlogwang    if (mp == NULL)
2702bfe3f2eSlogwang        rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
2712bfe3f2eSlogwang
2722bfe3f2eSlogwang    mz = rte_memzone_lookup(MZ_SHARED_INFO);
2732bfe3f2eSlogwang    if (mz == NULL)
2742bfe3f2eSlogwang        rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
2752bfe3f2eSlogwang    info = mz->addr;
2762bfe3f2eSlogwang    tx_stats = &(info->tx_stats[node_id]);
2772bfe3f2eSlogwang    filter_stats = &(info->filter_stats[node_id]);
2782bfe3f2eSlogwang
2792bfe3f2eSlogwangThen, the hash table that contains the flows that will be handled
2802bfe3f2eSlogwangby the node is created and populated.
2812bfe3f2eSlogwang
2822bfe3f2eSlogwang.. code-block:: c
2832bfe3f2eSlogwang
2842bfe3f2eSlogwang    static struct rte_hash *
2852bfe3f2eSlogwang    create_hash_table(const struct shared_info *info)
2862bfe3f2eSlogwang    {
2872bfe3f2eSlogwang        uint32_t num_flows_node = info->num_flows / info->num_nodes;
2882bfe3f2eSlogwang        char name[RTE_HASH_NAMESIZE];
2892bfe3f2eSlogwang        struct rte_hash *h;
2902bfe3f2eSlogwang
2912bfe3f2eSlogwang        /* create table */
2922bfe3f2eSlogwang        struct rte_hash_parameters hash_params = {
2932bfe3f2eSlogwang            .entries = num_flows_node * 2, /* table load = 50% */
2942bfe3f2eSlogwang            .key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
2952bfe3f2eSlogwang            .socket_id = rte_socket_id(),
2962bfe3f2eSlogwang            .hash_func_init_val = 0,
2972bfe3f2eSlogwang        };
2982bfe3f2eSlogwang
2992bfe3f2eSlogwang        snprintf(name, sizeof(name), "hash_table_%d", node_id);
3002bfe3f2eSlogwang        hash_params.name = name;
3012bfe3f2eSlogwang        h = rte_hash_create(&hash_params);
3022bfe3f2eSlogwang
3032bfe3f2eSlogwang        if (h == NULL)
3042bfe3f2eSlogwang            rte_exit(EXIT_FAILURE,
3052bfe3f2eSlogwang                    "Problem creating the hash table for node %d\n",
3062bfe3f2eSlogwang                    node_id);
3072bfe3f2eSlogwang        return h;
3082bfe3f2eSlogwang    }
3092bfe3f2eSlogwang
3102bfe3f2eSlogwang    static void
3112bfe3f2eSlogwang    populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
3122bfe3f2eSlogwang    {
3132bfe3f2eSlogwang        unsigned int i;
3142bfe3f2eSlogwang        int32_t ret;
3152bfe3f2eSlogwang        uint32_t ip_dst;
3162bfe3f2eSlogwang        uint32_t num_flows_node = 0;
3172bfe3f2eSlogwang        uint64_t target_node;
3182bfe3f2eSlogwang
3192bfe3f2eSlogwang        /* Add flows in table */
3202bfe3f2eSlogwang        for (i = 0; i < info->num_flows; i++) {
3212bfe3f2eSlogwang            target_node = i % info->num_nodes;
3222bfe3f2eSlogwang            if (target_node != node_id)
3232bfe3f2eSlogwang                continue;
3242bfe3f2eSlogwang
3252bfe3f2eSlogwang            ip_dst = rte_cpu_to_be_32(i);
3262bfe3f2eSlogwang
3272bfe3f2eSlogwang            ret = rte_hash_add_key(h, (void *) &ip_dst);
3282bfe3f2eSlogwang            if (ret < 0)
3292bfe3f2eSlogwang                rte_exit(EXIT_FAILURE, "Unable to add entry %u "
3302bfe3f2eSlogwang                        "in hash table\n", i);
3312bfe3f2eSlogwang            else
3322bfe3f2eSlogwang                num_flows_node++;
3332bfe3f2eSlogwang
3342bfe3f2eSlogwang        }
3352bfe3f2eSlogwang
3362bfe3f2eSlogwang        printf("Hash table: Adding 0x%x keys\n", num_flows_node);
3372bfe3f2eSlogwang    }
3382bfe3f2eSlogwang
3392bfe3f2eSlogwangAfter initialization, packets are dequeued from the shared ring
3402bfe3f2eSlogwang(from the server) and, like in the server process,
3412bfe3f2eSlogwangthe IPv4 address from the packets is used as a key to look up in the hash table.
3422bfe3f2eSlogwangIf there is a hit, packet is stored in a buffer, to be eventually transmitted
3432bfe3f2eSlogwangin one of the enabled ports. If key is not there, packet is dropped, since the
3442bfe3f2eSlogwangflow is not handled by the node.
3452bfe3f2eSlogwang
3462bfe3f2eSlogwang.. code-block:: c
3472bfe3f2eSlogwang
3482bfe3f2eSlogwang    static inline void
3492bfe3f2eSlogwang    handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
3502bfe3f2eSlogwang    {
3514418919fSjohnjiang        struct rte_ipv4_hdr *ipv4_hdr;
3522bfe3f2eSlogwang        uint32_t ipv4_dst_ip[PKT_READ_SIZE];
3532bfe3f2eSlogwang        const void *key_ptrs[PKT_READ_SIZE];
3542bfe3f2eSlogwang        unsigned int i;
3552bfe3f2eSlogwang        int32_t positions[PKT_READ_SIZE] = {0};
3562bfe3f2eSlogwang
3572bfe3f2eSlogwang        for (i = 0; i < num_packets; i++) {
3582bfe3f2eSlogwang            /* Handle IPv4 header.*/
3594418919fSjohnjiang            ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct rte_ipv4_hdr *,
3604418919fSjohnjiang                    sizeof(struct rte_ether_hdr));
3612bfe3f2eSlogwang            ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
3622bfe3f2eSlogwang            key_ptrs[i] = &ipv4_dst_ip[i];
3632bfe3f2eSlogwang        }
3642bfe3f2eSlogwang        /* Check if packets belongs to any flows handled by this node */
3652bfe3f2eSlogwang        rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
3662bfe3f2eSlogwang
3672bfe3f2eSlogwang        for (i = 0; i < num_packets; i++) {
3682bfe3f2eSlogwang            if (likely(positions[i] >= 0)) {
3692bfe3f2eSlogwang                filter_stats->passed++;
3702bfe3f2eSlogwang                transmit_packet(bufs[i]);
3712bfe3f2eSlogwang            } else {
3722bfe3f2eSlogwang                filter_stats->drop++;
3732bfe3f2eSlogwang                /* Drop packet, as flow is not handled by this node */
3742bfe3f2eSlogwang                rte_pktmbuf_free(bufs[i]);
3752bfe3f2eSlogwang            }
3762bfe3f2eSlogwang        }
3772bfe3f2eSlogwang    }
3782bfe3f2eSlogwang
3792bfe3f2eSlogwangFinally, note that both processes updates statistics, such as transmitted, received
3802bfe3f2eSlogwangand dropped packets, which are shown and refreshed by the server app.
3812bfe3f2eSlogwang
3822bfe3f2eSlogwang.. code-block:: c
3832bfe3f2eSlogwang
3842bfe3f2eSlogwang    static void
3852bfe3f2eSlogwang    do_stats_display(void)
3862bfe3f2eSlogwang    {
3872bfe3f2eSlogwang        unsigned int i, j;
3882bfe3f2eSlogwang        const char clr[] = {27, '[', '2', 'J', '\0'};
3892bfe3f2eSlogwang        const char topLeft[] = {27, '[', '1', ';', '1', 'H', '\0'};
3902bfe3f2eSlogwang        uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
3912bfe3f2eSlogwang        uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
3922bfe3f2eSlogwang
3932bfe3f2eSlogwang        /* to get TX stats, we need to do some summing calculations */
3942bfe3f2eSlogwang        memset(port_tx, 0, sizeof(port_tx));
3952bfe3f2eSlogwang        memset(port_tx_drop, 0, sizeof(port_tx_drop));
3962bfe3f2eSlogwang        memset(node_tx, 0, sizeof(node_tx));
3972bfe3f2eSlogwang        memset(node_tx_drop, 0, sizeof(node_tx_drop));
3982bfe3f2eSlogwang
3992bfe3f2eSlogwang        for (i = 0; i < num_nodes; i++) {
4002bfe3f2eSlogwang            const struct tx_stats *tx = &info->tx_stats[i];
4012bfe3f2eSlogwang
4022bfe3f2eSlogwang            for (j = 0; j < info->num_ports; j++) {
4032bfe3f2eSlogwang                const uint64_t tx_val = tx->tx[info->id[j]];
4042bfe3f2eSlogwang                const uint64_t drop_val = tx->tx_drop[info->id[j]];
4052bfe3f2eSlogwang
4062bfe3f2eSlogwang                port_tx[j] += tx_val;
4072bfe3f2eSlogwang                port_tx_drop[j] += drop_val;
4082bfe3f2eSlogwang                node_tx[i] += tx_val;
4092bfe3f2eSlogwang                node_tx_drop[i] += drop_val;
4102bfe3f2eSlogwang            }
4112bfe3f2eSlogwang        }
4122bfe3f2eSlogwang
4132bfe3f2eSlogwang        /* Clear screen and move to top left */
4142bfe3f2eSlogwang        printf("%s%s", clr, topLeft);
4152bfe3f2eSlogwang
4162bfe3f2eSlogwang        printf("PORTS\n");
4172bfe3f2eSlogwang        printf("-----\n");
4182bfe3f2eSlogwang        for (i = 0; i < info->num_ports; i++)
4192bfe3f2eSlogwang            printf("Port %u: '%s'\t", (unsigned int)info->id[i],
4202bfe3f2eSlogwang                    get_printable_mac_addr(info->id[i]));
4212bfe3f2eSlogwang        printf("\n\n");
4222bfe3f2eSlogwang        for (i = 0; i < info->num_ports; i++) {
4232bfe3f2eSlogwang            printf("Port %u - rx: %9"PRIu64"\t"
4242bfe3f2eSlogwang                    "tx: %9"PRIu64"\n",
4252bfe3f2eSlogwang                    (unsigned int)info->id[i], info->rx_stats.rx[i],
4262bfe3f2eSlogwang                    port_tx[i]);
4272bfe3f2eSlogwang        }
4282bfe3f2eSlogwang
4292bfe3f2eSlogwang        printf("\nSERVER\n");
4302bfe3f2eSlogwang        printf("-----\n");
4312bfe3f2eSlogwang        printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
4322bfe3f2eSlogwang                flow_dist_stats.distributed, flow_dist_stats.drop);
4332bfe3f2eSlogwang
4342bfe3f2eSlogwang        printf("\nNODES\n");
4352bfe3f2eSlogwang        printf("-------\n");
4362bfe3f2eSlogwang        for (i = 0; i < num_nodes; i++) {
4372bfe3f2eSlogwang            const unsigned long long rx = nodes[i].stats.rx;
4382bfe3f2eSlogwang            const unsigned long long rx_drop = nodes[i].stats.rx_drop;
4392bfe3f2eSlogwang            const struct filter_stats *filter = &info->filter_stats[i];
4402bfe3f2eSlogwang
4412bfe3f2eSlogwang            printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
4422bfe3f2eSlogwang                    "            tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
4432bfe3f2eSlogwang                    "            filter_passed: %9"PRIu64", "
4442bfe3f2eSlogwang                    "filter_drop: %9"PRIu64"\n",
4452bfe3f2eSlogwang                    i, rx, rx_drop, node_tx[i], node_tx_drop[i],
4462bfe3f2eSlogwang                    filter->passed, filter->drop);
4472bfe3f2eSlogwang        }
4482bfe3f2eSlogwang
4492bfe3f2eSlogwang        printf("\n");
4502bfe3f2eSlogwang    }
451