LCOV - code coverage report
Current view: top level - asiolibs/plugins - SocketWriterModule.cpp (source / functions) Coverage Total Hit
Test: code.result Lines: 0.0 % 178 0
Test Date: 2026-07-12 15:23:06 Functions: 0.0 % 40 0

            Line data    Source code
       1              : /**
       2              :  * @file SocketWriterModule.cpp Boost.Asio-based socket writer plugin for low-bandwidth devices
       3              :  *
       4              :  * This is part of the DUNE DAQ , copyright 2020.
       5              :  * Licensing/copyright details are in the COPYING file that you should have
       6              :  * received with this code.
       7              :  */
       8              : 
       9              : #include "SocketWriterModule.hpp"
      10              : 
      11              : #include "CreateGenericReceiver.hpp"
      12              : 
      13              : #include "appfwk/ConfigurationManager.hpp"
      14              : #include "appmodel/SocketDataSender.hpp"
      15              : #include "appmodel/NWDetDataReceiver.hpp"
      16              : #include "appmodel/SocketDetectorToDaqConnection.hpp"
      17              : #include "appmodel/SocketDataWriterModule.hpp"
      18              : #include "confmodel/DetectorStream.hpp"
      19              : #include "confmodel/QueueWithSourceId.hpp"
      20              : #include "confmodel/NetworkInterface.hpp"
      21              : 
      22              : #include "datahandlinglibs/DataHandlingIssues.hpp"
      23              : 
      24              : #include "asiolibs/opmon/SocketWriterModule.pb.h"
      25              : 
      26              : #include "asiolibs/AsioIssues.hpp"
      27              : 
      28              : #include <string>
      29              : #include <memory>
      30              : #include <vector>
      31              : #include <utility>
      32              : 
      33              : namespace dunedaq::asiolibs {
      34              : 
      35            0 : SocketWriterModule::SocketWriterModule(const std::string& name)
      36              :   : DAQModule(name)
      37            0 :   , m_work_guard(boost::asio::make_work_guard(m_io_context))
      38              : {
      39            0 :   register_command("conf", &SocketWriterModule::do_configure);
      40            0 :   register_command("start", &SocketWriterModule::do_start);
      41            0 :   register_command("stop_trigger_sources", &SocketWriterModule::do_stop);
      42            0 : }
      43              : 
      44              : void 
      45            0 : SocketWriterModule::get_dal_inputs(const dunedaq::appmodel::SocketDataWriterModule* mdal)
      46              : {
      47            0 :   auto* con = mdal->get_inputs()[0]; // there's only 1 input  
      48            0 :   auto* queue = con->cast<confmodel::Queue>();
      49            0 :   if (queue == nullptr) {
      50            0 :     auto err = datahandlinglibs::InitializationError(ERS_HERE, "Input is not of type Queue.");
      51            0 :     ers::fatal(err);
      52            0 :     throw err;
      53            0 :   }
      54              : 
      55            0 :   auto connection_name = queue->UID();
      56              : 
      57            0 :   const auto recv_timeout_ms = con->get_recv_timeout_ms();
      58            0 :   if (recv_timeout_ms == 0) {
      59            0 :     ers::warning(InvalidRawReceiverTimeout(ERS_HERE, m_receiver_timeout_ms.count()));
      60              :   } else {
      61            0 :     m_receiver_timeout_ms = std::chrono::milliseconds(recv_timeout_ms);
      62              :   }
      63              : 
      64            0 :   m_receiver = createGenericReceiver(connection_name);
      65              :   // Input connection sensibility check
      66            0 :   if (m_receiver == nullptr) {
      67              :     //ers::error(datahandlinglibs::ConfigurationError(ERS_HERE, sid, "Non callback mode, and receiver is unset!")); FIXME (DTE)
      68              :   }
      69            0 : }
      70              : 
      71              : void
      72            0 : SocketWriterModule::init(const std::shared_ptr<appfwk::ConfigurationManager> mcfg)
      73              : {
      74            0 :   auto* mdal = mcfg->get_dal<appmodel::SocketDataWriterModule>(get_name());
      75              : 
      76            0 :   get_dal_inputs(mdal);
      77              : 
      78            0 :   auto* d2d_conn = mdal->get_connection();
      79            0 :   auto* socket_d2d_conn = d2d_conn->cast<appmodel::SocketDetectorToDaqConnection>();
      80            0 :   if (socket_d2d_conn == nullptr) {
      81            0 :     auto err = datahandlinglibs::InitializationError(ERS_HERE, "Connection is not of type SocketDetectorToDaqConnection.");
      82            0 :     ers::fatal(err);
      83            0 :     throw err;
      84            0 :   } 
      85              : 
      86            0 :   auto nw_receiver = socket_d2d_conn->get_net_receiver();
      87            0 :   auto remote_ip = nw_receiver->get_uses()->get_ip_address()[0];
      88              : 
      89            0 :   auto* nw_sender = socket_d2d_conn->get_net_senders()[0]; // there's only 1 sender   
      90              : 
      91            0 :   auto* socket_sender = nw_sender->cast<appmodel::SocketDataSender>();
      92            0 :   if (socket_sender == nullptr) {
      93            0 :     auto err = datahandlinglibs::InitializationError(ERS_HERE, "Sender is not of type SocketDataSender.");
      94            0 :     ers::fatal(err);
      95            0 :     throw err;
      96            0 :   }  
      97              : 
      98            0 :   auto local_ip = socket_sender->get_uses()->get_ip_address()[0];
      99            0 :   auto local_port = socket_sender->get_local_port();
     100            0 :   auto remote_port = socket_sender->get_remote_port();
     101              :   
     102            0 :   m_writer_info = std::make_shared<WriterInfo>(local_ip, local_port, remote_ip, remote_port, std::make_shared<SocketStats>());
     103              :     
     104            0 :   if (string_to_socket_type(socket_sender->get_socket_type()) == SocketType::TCP) {
     105            0 :     m_writer = std::make_shared<std::variant<TCPWriter, UDPWriter>>(TCPWriter{});
     106              :   } else {
     107            0 :     m_writer = std::make_shared<std::variant<TCPWriter, UDPWriter>>(UDPWriter{});      
     108              :   }    
     109            0 : }
     110              : 
     111              : SocketWriterModule::SocketType
     112            0 : SocketWriterModule::string_to_socket_type(const std::string& socket_type) const
     113              : {
     114            0 :   if (socket_type == "TCP") {
     115              :     return SocketWriterModule::SocketType::TCP;
     116            0 :   } else if (socket_type == "UDP") {
     117            0 :     return SocketWriterModule::SocketType::UDP;
     118              :   }
     119              :   return SocketWriterModule::SocketType::INVALID;
     120              : }
     121              : 
     122              : void
     123            0 : SocketWriterModule::do_configure(const CommandData_t&)
     124              : {
     125            0 :   std::visit([this](auto& writer) { writer.configure(m_io_context, m_writer_info); }, *m_writer);    
     126            0 : }
     127              : 
     128              : void
     129            0 : SocketWriterModule::do_start(const CommandData_t&)
     130              : {
     131              :   // Reset opmon variables
     132            0 :   m_writer_info->socket_stats->sum_payloads = 0;
     133            0 :   m_writer_info->socket_stats->num_payloads = 0;
     134            0 :   m_writer_info->socket_stats->sum_bytes = 0;
     135            0 :   m_writer_info->socket_stats->stats_packet_count = 0;
     136              : 
     137            0 :   m_t0 = std::chrono::steady_clock::now();
     138              : 
     139            0 :   m_run_marker.store(true);
     140            0 :   m_consumer_thread.set_work(&SocketWriterModule::run_consume, this);
     141              : 
     142            0 :   m_io_thread = std::jthread([this] { m_io_context.run(); });
     143            0 : }
     144              : 
     145              : void 
     146            0 : SocketWriterModule::run_consume()
     147              : {
     148            0 :   TLOG() << "Consumer thread started..."; // TODO (DTE): Make debug logs
     149              : 
     150            0 :   while (m_run_marker.load()) {
     151              :     // Try to acquire data
     152            0 :     if (auto opt_payload = m_receiver->try_receive(m_receiver_timeout_ms)) {
     153            0 :       std::visit([&](auto& writer) { writer.enqueue(std::move(*opt_payload)); }, *m_writer);
     154              :     } else {
     155            0 :       std::visit([&](auto& writer) { ++writer.get_socket_stats()->queue_timeout_count; }, *m_writer);
     156            0 :     }
     157              :   }
     158              : 
     159            0 :   TLOG() << "Consumer thread joins... ";
     160            0 : }
     161              : 
     162              : 
     163              : void
     164            0 : SocketWriterModule::do_stop(const CommandData_t&)
     165              : {
     166            0 :   m_run_marker.store(false);
     167            0 :   while (!m_consumer_thread.get_readiness()) {
     168            0 :     std::this_thread::sleep_for(std::chrono::milliseconds(10));
     169              :   }
     170              : 
     171            0 :   std::visit([this](auto& writer) { writer.stop(); }, *m_writer);
     172              : 
     173            0 :   m_work_guard.reset();
     174            0 : }
     175              : 
     176              : void
     177            0 : SocketWriterModule::generate_opmon_data()
     178              : {
     179            0 :   opmon::SocketWriterStats stats;
     180            0 :   stats.set_sum_payloads(m_writer_info->socket_stats->sum_payloads.load());
     181            0 :   stats.set_num_payloads(m_writer_info->socket_stats->num_payloads.exchange(0));
     182            0 :   stats.set_sum_bytes(m_writer_info->socket_stats->sum_bytes.load());
     183              : 
     184            0 :   auto now = std::chrono::steady_clock::now();
     185            0 :   int new_packets = m_writer_info->socket_stats->stats_packet_count.exchange(0);
     186            0 :   double seconds = std::chrono::duration_cast<std::chrono::microseconds>(now - m_t0).count() / 1000000.;
     187            0 :   m_t0 = now;
     188              : 
     189            0 :   stats.set_rate_payloads_consumed(new_packets / seconds / 1000.);
     190              : 
     191            0 :   publish(std::move(stats), { { "socket-writer", m_writer_info->local_ip + ":" + std::to_string(m_writer_info->local_port) } });
     192            0 : }
     193              : 
     194              : void
     195            0 : SocketWriterModule::TCPWriter::configure(boost::asio::io_context& io_context, std::shared_ptr<WriterInfo> writer_info)
     196              : {
     197            0 :   m_io_context = &io_context;
     198            0 :   m_strand = std::make_shared<boost::asio::strand<boost::asio::io_context::executor_type>>(io_context.get_executor());  
     199              : 
     200            0 :   m_socket_stats = writer_info->socket_stats;
     201              : 
     202            0 :   boost::asio::ip::address local_address;
     203            0 :   boost::asio::ip::address remote_address;
     204              : 
     205            0 :   try {
     206            0 :     local_address = boost::asio::ip::make_address(writer_info->local_ip);
     207            0 :     remote_address = boost::asio::ip::make_address(writer_info->remote_ip);
     208            0 :   } catch (const boost::system::system_error& e) {
     209            0 :     TLOG() << "Failed to configure TCP socket: " << e.what()
     210            0 :       << " (Local IP: " << writer_info->local_ip << ", Remote IP: " << writer_info->remote_ip  << ")";
     211            0 :     throw;
     212            0 :   }
     213              : 
     214            0 :   while (true) {
     215            0 :     try {
     216            0 :       m_socket = std::make_unique<boost::asio::ip::tcp::socket>(io_context);
     217            0 :       m_socket->open(boost::asio::ip::tcp::v4());      
     218            0 :       m_socket->bind(boost::asio::ip::tcp::endpoint(local_address, writer_info->local_port));
     219            0 :       m_socket->connect(boost::asio::ip::tcp::endpoint(remote_address, writer_info->remote_port));
     220            0 :       break;
     221            0 :     } catch (const boost::system::system_error& e) {
     222            0 :       TLOG() << "Failed to create TCP socket: " << e.what() << ". Retrying in 1 second..."
     223            0 :         << " (Local IP: " << writer_info->local_ip << ", Local port: " << writer_info->local_port
     224            0 :         << ", Remote IP: " << writer_info->remote_ip << ", Remote port: " << writer_info->remote_port << ")";      
     225            0 :       std::this_thread::sleep_for(std::chrono::seconds(1));
     226            0 :     }
     227              :   }
     228              : 
     229            0 :   TLOG() << "Established TCP connection from "
     230            0 :     << m_socket->local_endpoint().address().to_string() << ":" << m_socket->local_endpoint().port()
     231            0 :     << " to "
     232            0 :     << m_socket->remote_endpoint().address().to_string() << ":" << m_socket->remote_endpoint().port();  
     233            0 : }
     234              : 
     235              : void
     236            0 : SocketWriterModule::TCPWriter::enqueue(GenericReceiverConcept::TypeErasedPayload&& payload)
     237              : {
     238            0 :   boost::asio::post(*m_strand, [this, payload = std::move(payload)]() mutable {
     239            0 :     m_payloads.push(std::move(payload));
     240            0 :     if (m_payloads.size() == 1) { // queue was empty
     241            0 :       boost::asio::co_spawn(*m_strand, start(), boost::asio::detached);
     242              :     }
     243            0 :   });  
     244            0 : }
     245              : 
     246              : boost::asio::awaitable<void>
     247            0 : SocketWriterModule::TCPWriter::start()
     248              : {
     249              :   while (!m_payloads.empty()) {
     250              :     auto payload = std::move(m_payloads.front());
     251              :     
     252              :     auto bytes_sent =
     253              :     co_await boost::asio::async_write(*m_socket, boost::asio::buffer(payload.data, payload.size), boost::asio::use_awaitable);
     254              :     m_payloads.pop();
     255              : 
     256              :     ++m_socket_stats->num_payloads;
     257              :     ++m_socket_stats->sum_payloads;
     258              :     m_socket_stats->sum_bytes.fetch_add(bytes_sent);
     259              :     ++m_socket_stats->stats_packet_count;
     260              :   }
     261            0 : }
     262              : 
     263              : void
     264            0 : SocketWriterModule::TCPWriter::stop()
     265              : {
     266            0 :   if (m_socket && m_socket->is_open()) {
     267            0 :     m_socket->close();
     268              :   }
     269            0 :   TLOG() << "Closed TCP connection";
     270            0 : }
     271              : 
     272              : std::shared_ptr<SocketWriterModule::SocketStats>
     273            0 : SocketWriterModule::TCPWriter::get_socket_stats() const
     274              : {
     275            0 :   return m_socket_stats;  
     276              : }
     277              : 
     278              : void
     279            0 : SocketWriterModule::UDPWriter::configure(boost::asio::io_context& io_context, std::shared_ptr<WriterInfo> writer_info)
     280              : {
     281            0 :   m_io_context = &io_context;
     282            0 :   m_strand = std::make_shared<boost::asio::strand<boost::asio::io_context::executor_type>>(io_context.get_executor());  
     283              : 
     284            0 :   m_socket_stats = writer_info->socket_stats;
     285              : 
     286            0 :   try {
     287            0 :     boost::asio::ip::udp::endpoint writer_endpoint(
     288            0 :       boost::asio::ip::address::from_string(writer_info->local_ip), writer_info->local_port);
     289              : 
     290            0 :     m_socket = std::make_unique<boost::asio::ip::udp::socket>(io_context, writer_endpoint);     
     291              : 
     292            0 :     m_remote_endpoint = boost::asio::ip::udp::endpoint(
     293            0 :       boost::asio::ip::address::from_string(writer_info->remote_ip), writer_info->remote_port);
     294              : 
     295            0 :     TLOG() << "Created UDP socket on "
     296            0 :       << m_socket->local_endpoint().address() << ":" << m_socket->local_endpoint().port(); 
     297              : 
     298            0 :   } catch (const boost::system::system_error& e) {
     299            0 :     TLOG() << "Failed to create UDP socket: " << e.what()
     300            0 :       << " (Local IP: " << writer_info->local_ip << ", Local port: " << writer_info->local_port
     301            0 :       << ", Remote IP: " << writer_info->remote_ip << ", Remote port: " << writer_info->remote_port << ")";
     302            0 :     throw;
     303            0 :   }          
     304            0 : }
     305              : 
     306              : void
     307            0 : SocketWriterModule::UDPWriter::enqueue(GenericReceiverConcept::TypeErasedPayload&& payload)
     308              : {
     309            0 :   boost::asio::post(*m_strand, [this, payload = std::move(payload)]() mutable {
     310            0 :     m_payloads.push(std::move(payload));
     311            0 :     if (m_payloads.size() == 1) { // queue was empty
     312            0 :       boost::asio::co_spawn(*m_strand, start(), boost::asio::detached);
     313              :     }
     314            0 :   });    
     315            0 : }
     316              : 
     317              : boost::asio::awaitable<void>
     318            0 : SocketWriterModule::UDPWriter::start()
     319              : {
     320              :   while (!m_payloads.empty()) {
     321              :     auto payload = std::move(m_payloads.front());
     322              :     
     323              :     const auto bytes_sent = co_await m_socket->async_send_to(
     324              :       boost::asio::buffer(payload.data, payload.size), m_remote_endpoint, boost::asio::use_awaitable);
     325              :     m_payloads.pop();   
     326              : 
     327              :     ++m_socket_stats->num_payloads;
     328              :     ++m_socket_stats->sum_payloads;
     329              :     m_socket_stats->sum_bytes.fetch_add(bytes_sent);
     330              :     ++m_socket_stats->stats_packet_count;  
     331              :   }
     332            0 : }
     333              : 
     334              : void
     335            0 : SocketWriterModule::UDPWriter::stop()
     336              : {
     337            0 :   if (m_socket && m_socket->is_open()) {
     338            0 :     m_socket->close();
     339              :   }
     340            0 :   TLOG() << "Closed UDP socket";
     341            0 : }
     342              : 
     343              : std::shared_ptr<SocketWriterModule::SocketStats>
     344            0 : SocketWriterModule::UDPWriter::get_socket_stats() const
     345              : {
     346            0 :   return m_socket_stats;  
     347              : }
     348              : 
     349              : } // namespace dunedaq::asiolibs
     350              : 
     351            0 : DEFINE_DUNE_DAQ_MODULE(dunedaq::asiolibs::SocketWriterModule)
        

Generated by: LCOV version 2.0-1