LCOV - code coverage report
Current view: top level - dpdklibs/test/apps - test_transmit_and_receive.cxx (source / functions) Coverage Total Hit
Test: code.result Lines: 0.0 % 156 0
Test Date: 2026-10-11 15:05:20 Functions: 0.0 % 17 0

            Line data    Source code
       1              : /**
       2              :  *
       3              :  * @file test_transmit_and_receive.cxx
       4              :  *
       5              :  * This is a standalone program which will transmit ethernet packets
       6              :  * out on one dpdk-enabled network interface port and receive them on
       7              :  * another. Obviously you actually need two such ports for this to
       8              :  * work.
       9              :  *
      10              :  *
      11              :  * This is part of the DUNE DAQ Application Framework, copyright 2020.
      12              :  * Licensing/copyright details are in the COPYING file that you should have
      13              :  * received with this code.
      14              :  */
      15              : 
      16              : #include "dpdklibs/EALSetup.hpp"
      17              : #include "dpdklibs/udp/PacketCtor.hpp"
      18              : #include "dpdklibs/udp/Utils.hpp"
      19              : 
      20              : #include "detdataformats/DAQEthHeader.hpp"
      21              : #include "logging/Logging.hpp"
      22              : 
      23              : #include "rte_common.h"
      24              : #include "rte_eal.h"
      25              : #include "rte_ethdev.h"
      26              : 
      27              : #include "CLI/App.hpp"
      28              : #include "CLI/Config.hpp"
      29              : #include "CLI/Formatter.hpp"
      30              : 
      31              : #include <limits>
      32              : #include <mutex>
      33              : 
      34              : using namespace dunedaq::dpdklibs;
      35              : using dunedaq::detdataformats::DAQEthHeader;
      36              : 
      37              : namespace {
      38              : 
      39              : constexpr int burst_size = 256;
      40              : 
      41              : std::mutex lcore_thread_mutex;
      42              : 
      43              : int payload_bytes = 9000;
      44              : 
      45              : std::atomic<long> num_sent_bursts = 0;
      46              : std::atomic<long> num_sent_packets = 0;
      47              : std::atomic<long> num_received_packets = 0;
      48              : std::atomic<long> num_sent_bytes = 0;
      49              : std::atomic<long> num_received_bytes = 0;
      50              : std::atomic<long> num_bad_packets = 0;
      51              : 
      52              : }; // namespace
      53              : 
      54              : struct lcore_thread_arg
      55              : {
      56              :   rte_mempool* pool;
      57              :   int iface_id;
      58              :   bool is_receiver;
      59              : };
      60              : 
      61              : inline bool
      62            0 : operator!=(const DAQEthHeader& header1, const DAQEthHeader& header2)
      63              : {
      64            0 :   return (header1.version != header2.version || header1.det_id != header2.det_id ||
      65            0 :           header1.crate_id != header2.crate_id || header1.slot_id != header2.slot_id ||
      66            0 :           header1.stream_id != header2.stream_id || header1.seq_id != header2.seq_id ||
      67            0 :           header1.block_length != header2.block_length || header1.timestamp != header2.timestamp);
      68              : }
      69              : 
      70              : int
      71            0 : lcore_main(void* arg)
      72              : {
      73              : 
      74            0 :   uint16_t lid = rte_lcore_id();
      75              : 
      76              :   // 0: main thread (doesn't use this function), 1: receiver thread, 2: transmitter thread
      77            0 :   if (lid == 0 || lid >= 3) {
      78              :     return 0;
      79              :   }
      80              : 
      81            0 :   TLOG() << "From lcore_main, lid == " << lid << ", rte_socket_id() == " << rte_socket_id();
      82              : 
      83            0 :   lcore_thread_arg info;
      84            0 :   const auto ptr_to_vector_of_thread_configs = static_cast<std::vector<lcore_thread_arg>*>(arg);
      85              : 
      86            0 :   {
      87            0 :     std::lock_guard<std::mutex> guard(lcore_thread_mutex);
      88            0 :     info = ptr_to_vector_of_thread_configs->at(lid - 1);
      89            0 :   }
      90            0 :   TLOG() << "In lcore_main, thread #" << lid << ", iface_id == " << info.iface_id
      91            0 :          << ", is_receiver == " << info.is_receiver;
      92              : 
      93            0 :   if (rte_eth_dev_socket_id(info.iface_id) >= 0 &&
      94            0 :       rte_eth_dev_socket_id(info.iface_id) != static_cast<int>(rte_socket_id())) {
      95            0 :     TLOG(TLVL_WARNING) << "WARNING, dpdk interface " << info.iface_id
      96            0 :                        << " is on remote NUMA node to polling thread.\nrte_eth_dev_socket_id(" << info.iface_id
      97            0 :                        << ") == " << rte_eth_dev_socket_id(info.iface_id)
      98            0 :                        << ", rte_socket_id() == " << static_cast<int>(rte_socket_id())
      99            0 :                        << "\nPerformance will not be optimal.\n";
     100              :   }
     101              : 
     102            0 :   auto bufs = static_cast<rte_mbuf**>(malloc(sizeof(struct rte_mbuf*) * burst_size));
     103              : 
     104            0 :   if (bufs == NULL) {
     105            0 :     TLOG(TLVL_ERROR) << "Unable to allocate memory for buffers; returning from thread...";
     106            0 :     return 1;
     107              :   }
     108              : 
     109            0 :   int retval = 0;
     110            0 :   retval = rte_pktmbuf_alloc_bulk(info.pool, bufs, burst_size);
     111              : 
     112            0 :   if (retval != 0) {
     113            0 :     std::stringstream errstr;
     114            0 :     errstr << "A failure occurred calling rte_pktmbuf_alloc_bulk (" << strerror(abs(retval))
     115            0 :            << "); returning from thread...";
     116            0 :     return 2;
     117            0 :   }
     118              : 
     119            0 :   if (info.is_receiver) {
     120              : 
     121            0 :     uint16_t nb_rx = 0;
     122            0 :     int bytes_in_burst = 0;
     123            0 :     int bad_packets_in_burst = 0;
     124              : 
     125              :     // Here, I'm taking advantage of the fact that the transmitting
     126              :     // thread calls construct_packets_for_burst, which itself fills
     127              :     // the DAQEthHeader structure inside the packet it sends using
     128              :     // "set_daqethheader_test_values". I'll compare the received
     129              :     // packet's DAQEthHeader to this one and make sure they're equal
     130              : 
     131            0 :     DAQEthHeader daqethheader_reference;
     132            0 :     udp::set_daqethheader_test_values(daqethheader_reference);
     133            0 :     DAQEthHeader* daqethheader_ptr = nullptr;
     134              : 
     135            0 :     while (true) {
     136              : 
     137            0 :       bytes_in_burst = 0; // Quicker to add up the bytes in the packets in a local variables, then attach the total to
     138              :                           // the atomic variable used in stats reporting
     139            0 :       bad_packets_in_burst = 0; // Same thinking
     140              : 
     141            0 :       nb_rx = rte_eth_rx_burst(info.iface_id, 0, bufs, burst_size);
     142            0 :       num_received_packets += nb_rx;
     143              : 
     144            0 :       for (int i_p = 0; i_p < nb_rx; ++i_p) {
     145            0 :         bytes_in_burst += bufs[i_p]->pkt_len;
     146              : 
     147              :         // DAQEthHeader (should be) the first thing after the ethernet + IPv4 + UDP headers
     148            0 :         daqethheader_ptr = rte_pktmbuf_mtod_offset(bufs[0], DAQEthHeader*, sizeof(udp::ipv4_udp_packet_hdr));
     149            0 :         if (*daqethheader_ptr != daqethheader_reference) {
     150            0 :           bad_packets_in_burst++;
     151              :         }
     152              :       }
     153              : 
     154            0 :       num_received_bytes += bytes_in_burst;
     155            0 :       num_bad_packets += bad_packets_in_burst;
     156              : 
     157              :       // Free any unsent packets
     158            0 :       if (unlikely(nb_rx < burst_size)) {
     159            0 :         rte_pktmbuf_free_bulk(bufs, burst_size - nb_rx);
     160              :       }
     161              :     }
     162              : 
     163              :   } else {
     164              : 
     165            0 :     rte_ether_addr dst_mac_addr_struct;
     166              : 
     167              :     // Notice that in the snippet of code below, the assumption is that
     168              :     // the first entry in the vector of thread configurations
     169              :     // corresponds to the receiving thread
     170            0 :     uint16_t receiver_iface = std::numeric_limits<uint16_t>::max();
     171            0 :     {
     172            0 :       std::lock_guard<std::mutex> guard(lcore_thread_mutex);
     173            0 :       receiver_iface = ptr_to_vector_of_thread_configs->at(0).iface_id;
     174            0 :     }
     175              : 
     176            0 :     retval = rte_eth_macaddr_get(receiver_iface, &dst_mac_addr_struct);
     177              : 
     178            0 :     if (retval == 0) {
     179            0 :       TLOG() << "Will send to ethernet interface with MAC address " << ealutils::get_mac_addr_str(dst_mac_addr_struct);
     180              :     } else {
     181            0 :       TLOG() << "Problem trying to obtain the MAC address of the destination port; returning...";
     182            0 :       return 3;
     183              :     }
     184              : 
     185            0 :     const std::string dst_mac_addr = ealutils::get_mac_addr_str(dst_mac_addr_struct);
     186            0 :     udp::construct_packets_for_burst(info.iface_id, dst_mac_addr, payload_bytes, burst_size, bufs);
     187              : 
     188            0 :     TLOG() << "Dump of the first packet header: ";
     189            0 :     TLOG() << udp::get_udp_header_str(bufs[0]);
     190              : 
     191            0 :     TLOG() << "Dump of the first packet rte_mbuf object: ";
     192            0 :     TLOG() << udp::get_rte_mbuf_str(bufs[0]);
     193              : 
     194            0 :     rte_mbuf_sanity_check(bufs[0], 1);
     195              : 
     196            0 :     uint16_t nb_tx = 0;
     197              : 
     198            0 :     while (true) {
     199              : 
     200            0 :       int queue_id = 0;
     201              :       // TLOG() << "Just about to call rte_eth_tx_burst with arguments info.iface_id == " << info.iface_id << ", second
     202              :       // argument " << queue_id << ", bufs at " << bufs << " and burst size " << burst_size;
     203            0 :       nb_tx = rte_eth_tx_burst(info.iface_id, queue_id, bufs, burst_size);
     204            0 :       num_sent_packets += nb_tx;
     205            0 :       num_sent_bytes += nb_tx * bufs[0]->pkt_len;
     206            0 :       num_sent_bursts++;
     207              :       // TLOG() << "Sent " << nb_tx;
     208              : 
     209              :       // Free any unsent packets
     210            0 :       if (unlikely(nb_tx < burst_size)) {
     211            0 :         rte_pktmbuf_free_bulk(bufs, burst_size - nb_tx);
     212              :       }
     213              :     }
     214            0 :   }
     215              :   return 0;
     216              : }
     217              : 
     218              : int
     219            0 : main(int argc, char** argv)
     220              : {
     221              : 
     222            0 :   CLI::App app{ "test transmit and receive" };
     223              : 
     224            0 :   std::stringstream payload_desc;
     225            0 :   payload_desc << "Bytes of payload past the DAQEthHeader header (default " << payload_bytes << " bytes)";
     226            0 :   app.add_option("--payload", payload_bytes, payload_desc.str());
     227              : 
     228            0 :   CLI11_PARSE(app, argc, argv);
     229              : 
     230            0 :   argc = 1; // So rte_eal_init doesn't interpret what gets passed to the command line
     231              : 
     232            0 :   int retval = rte_eal_init(argc, argv);
     233              : 
     234            0 :   if (retval < 0) {
     235            0 :     rte_exit(EXIT_FAILURE, "ERROR: EAL initialization failed, info: %s\n", strerror(abs(retval)));
     236              :   }
     237              : 
     238            0 :   int nb_ports = rte_eth_dev_count_avail();
     239            0 :   TLOG() << "There are " << nb_ports << " ethernet ports available out of a total of " << rte_eth_dev_count_total();
     240              : 
     241            0 :   if (nb_ports < 2) {
     242            0 :     rte_exit(EXIT_FAILURE,
     243              :              "ERROR: fewer than 2 ethernet ports are available.\nYou need one port for transmitting packets and one "
     244              :              "port for receiving them\n");
     245              :   }
     246              : 
     247            0 :   auto stats = std::thread([&]() {
     248            0 :     long sleep_time = 1;
     249            0 :     while (true) {
     250            0 :       TLOG() << "\nRates: \n"
     251            0 :              << num_sent_bursts / static_cast<double>(sleep_time) << " sent bursts/s\n"
     252            0 :              << num_sent_packets / static_cast<double>(sleep_time) << " sent packets/s\n"
     253            0 :              << num_received_packets / static_cast<double>(sleep_time) << " received packets/s\n"
     254            0 :              << num_bad_packets / static_cast<double>(sleep_time) << " bad packets/s\n"
     255            0 :              << num_sent_bytes / static_cast<double>(sleep_time) << " sent bytes/s\n"
     256            0 :              << num_received_bytes / static_cast<double>(sleep_time) << " received bytes/s\n";
     257            0 :       num_sent_packets.exchange(0);
     258            0 :       num_sent_bytes.exchange(0);
     259            0 :       num_sent_bursts.exchange(0);
     260            0 :       num_received_packets.exchange(0);
     261            0 :       num_received_bytes.exchange(0);
     262            0 :       num_bad_packets.exchange(0);
     263            0 :       std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
     264            0 :     }
     265            0 :   });
     266              : 
     267            0 :   uint16_t portid = std::numeric_limits<uint16_t>::max();
     268            0 :   int n_rx_qs = 0;
     269            0 :   int n_tx_qs = 0;
     270            0 :   uint16_t rx_ring_size = 1024;
     271            0 :   uint16_t tx_ring_size = 1024;
     272            0 :   int port_cntr = -1;
     273            0 :   std::map<int, std::unique_ptr<rte_mempool>> mbuf_pools;
     274            0 :   std::vector<lcore_thread_arg> lcore_thread_args;
     275              : 
     276            0 :   RTE_ETH_FOREACH_DEV(portid)
     277              :   { // RTE_ETH_FOREACH_DEV since no guarantee dpdk counts port IDs as 0, 1, 2 ...
     278              : 
     279            0 :     port_cntr++; // Initialize at -1, start at 0
     280              : 
     281              :     // Right now (May-2-2023) iface_init's implementation explicitly
     282              :     // assumes that the pools corresponding to <N> receiver queues
     283              :     // correspond to keys 0...N-1 in a map whose values are
     284              :     // unique_ptrs to pools
     285              : 
     286            0 :     std::stringstream poolname;
     287            0 :     poolname << "MBP-" << portid;
     288            0 :     mbuf_pools[port_cntr] = ealutils::get_mempool(poolname.str());
     289              : 
     290            0 :     bool is_receiver = false;
     291              : 
     292            0 :     if (port_cntr == 0) {
     293              :       n_tx_qs = 0;
     294              :       n_rx_qs = 1;
     295              :       is_receiver = true;
     296            0 :     } else if (port_cntr == 1) {
     297            0 :       n_tx_qs = 1;
     298            0 :       n_rx_qs = 0;
     299            0 :       is_receiver = false;
     300              :     }
     301              : 
     302            0 :     retval = ealutils::iface_init(portid, n_rx_qs, n_tx_qs, rx_ring_size, tx_ring_size, mbuf_pools);
     303              : 
     304            0 :     if (retval != 0) {
     305            0 :       rte_eal_cleanup();
     306              : 
     307            0 :       std::stringstream errstr;
     308            0 :       errstr << "A failure occurred initializing ethernet interface #" << portid << " (" << strerror(abs(retval))
     309            0 :              << "); exiting...";
     310            0 :       rte_exit(EXIT_FAILURE, errstr.str().c_str());
     311            0 :     }
     312              : 
     313            0 :     auto pool = mbuf_pools[port_cntr].release();
     314            0 :     mbuf_pools.erase(mbuf_pools.find(port_cntr));
     315              : 
     316            0 :     lcore_thread_args.emplace_back(lcore_thread_arg{ pool, portid, is_receiver });
     317            0 :   }
     318              : 
     319            0 :   rte_eal_mp_remote_launch(lcore_main, &lcore_thread_args, SKIP_MAIN);
     320              : 
     321            0 :   rte_eal_mp_wait_lcore();
     322            0 :   rte_eal_cleanup();
     323              : 
     324            0 :   int mynum = 55;
     325            0 :   TLOG() << "Things are still OK at this line, and here's a number: " << mynum;
     326              : 
     327            0 :   return 0;
     328            0 : }
        

Generated by: LCOV version 2.0-1