00001 #define TRACE_NAME "MulticastTransfer"
00002
00003 #include "artdaq/TransferPlugins/TransferInterface.hh"
00004
00005 #include "artdaq-core/Data/Fragment.hh"
00006 #include "artdaq-core/Utilities/ExceptionHandler.hh"
00007
00008 #include "fhiclcpp/ParameterSet.h"
00009 #include "cetlib_except/exception.h"
00010
00011 #include <boost/asio.hpp>
00012 #include <boost/bind.hpp>
00013
00014 #include <iostream>
00015 #include <vector>
00016 #include <cassert>
00017 #include <string>
00018 #include <type_traits>
00019 #include <bitset>
00020
00021 #pragma GCC diagnostic push
00022 #pragma GCC diagnostic ignored "-Wunused-parameter"
00023
00024
00025 namespace artdaq
00026 {
00030 class MulticastTransfer : public TransferInterface
00031 {
00032 public:
00033
00034 using byte_t = artdaq::Fragment::byte_t;
00035
00039 virtual ~MulticastTransfer() = default;
00040
00058 MulticastTransfer(fhicl::ParameterSet const& ps, Role role);
00059
00066 int receiveFragment(artdaq::Fragment& fragment,
00067 size_t receiveTimeout) override;
00068
00075 int receiveFragmentHeader(detail::RawFragmentHeader& header, size_t receiveTimeout) override;
00076
00083 int receiveFragmentData(RawDataType* destination, size_t wordCount) override;
00084
00090 CopyStatus copyFragment(artdaq::Fragment& fragment, size_t send_timeout_usec) override;
00091
00097 CopyStatus moveFragment(artdaq::Fragment&& fragment) override;
00098
00103 bool isRunning() override { return socket_ != nullptr; }
00104 private:
00105
00106 void fill_staging_memory(const artdaq::Fragment& frag);
00107
00108 template <typename T>
00109 void book_container_of_buffers(std::vector<T>& buffers,
00110 const size_t fragment_size,
00111 const size_t total_subfragments,
00112 const size_t first_subfragment_num,
00113 const size_t last_subfragment_num);
00114
00115 void get_fragment_quantities(const boost::asio::mutable_buffer& buf, size_t& payload_size, size_t& fragment_size,
00116 size_t& expected_subfragments);
00117
00118 void set_receive_buffer_size(size_t recv_buff_size);
00119
00120 class subfragment_identifier
00121 {
00122 public:
00123
00124 subfragment_identifier(size_t sequenceID, size_t fragmentID, size_t subfragment_number) :
00125 sequenceID_(sequenceID)
00126 , fragmentID_(fragmentID)
00127 , subfragment_number_(subfragment_number) { }
00128
00129 size_t sequenceID() const { return sequenceID_; }
00130 size_t fragmentID() const { return fragmentID_; }
00131 size_t subfragment_number() const { return subfragment_number_; }
00132
00133 private:
00134 size_t sequenceID_;
00135 size_t fragmentID_;
00136 size_t subfragment_number_;
00137 };
00138
00139 std::unique_ptr<boost::asio::io_service> io_service_;
00140
00141 std::unique_ptr<boost::asio::ip::udp::endpoint> local_endpoint_;
00142 std::unique_ptr<boost::asio::ip::udp::endpoint> multicast_endpoint_;
00143 std::unique_ptr<boost::asio::ip::udp::endpoint> opposite_endpoint_;
00144
00145 std::unique_ptr<boost::asio::ip::udp::socket> socket_;
00146
00147 size_t subfragment_size_;
00148 size_t subfragments_per_send_;
00149
00150 size_t pause_on_copy_usecs_;
00151 Fragment fragment_buffer_;
00152
00153 std::vector<byte_t> staging_memory_;
00154
00155 std::vector<boost::asio::mutable_buffer> receive_buffers_;
00156 };
00157 }
00158
00159 artdaq::MulticastTransfer::MulticastTransfer(fhicl::ParameterSet const& pset, Role role) :
00160 TransferInterface(pset, role)
00161 , io_service_(std::make_unique<std::remove_reference<decltype(*io_service_)>::type>())
00162 , local_endpoint_(nullptr)
00163 , multicast_endpoint_(nullptr)
00164 , opposite_endpoint_(std::make_unique<std::remove_reference<decltype(*opposite_endpoint_)>::type>())
00165 , socket_(nullptr)
00166 , subfragment_size_(pset.get<size_t>("subfragment_size"))
00167 , subfragments_per_send_(pset.get<size_t>("subfragments_per_send"))
00168 , pause_on_copy_usecs_(pset.get<size_t>("pause_on_copy_usecs", 0))
00169 {
00170 try
00171 {
00172 auto port = pset.get<unsigned short>("multicast_port");
00173 auto multicast_address = boost::asio::ip::address::from_string(pset.get<std::string>("multicast_address"));
00174 auto local_address = boost::asio::ip::address::from_string(pset.get<std::string>("local_address"));
00175
00176 TLOG(TLVL_DEBUG) << GetTraceName() << ": multicast address is set to " << multicast_address ;
00177 TLOG(TLVL_DEBUG) << GetTraceName() << ": local address is set to " << local_address ;
00178
00179 if (TransferInterface::role() == Role::kSend)
00180 {
00181 local_endpoint_ = std::make_unique<std::remove_reference<decltype(*local_endpoint_)>::type>(local_address, 0);
00182 multicast_endpoint_ = std::make_unique<std::remove_reference<decltype(*multicast_endpoint_)>::type>(multicast_address, port);
00183
00184 socket_ = std::make_unique<std::remove_reference<decltype(*socket_)>::type>(*io_service_,
00185 multicast_endpoint_->protocol());
00186 socket_->bind(*local_endpoint_);
00187 }
00188 else
00189 {
00190
00191
00192
00193 local_endpoint_ = std::make_unique<std::remove_reference<decltype(*local_endpoint_)>::type>(local_address, port);
00194 socket_ = std::make_unique<std::remove_reference<decltype(*socket_)>::type>(*io_service_,
00195 local_endpoint_->protocol());
00196
00197 boost::system::error_code ec;
00198
00199 socket_->set_option(boost::asio::ip::udp::socket::reuse_address(true), ec);
00200
00201 if (ec != 0)
00202 {
00203 std::cerr << "boost::system::error_code with value " << ec << " was found in setting reuse_address option" << std::endl;
00204 }
00205
00206 set_receive_buffer_size(pset.get<size_t>("receive_buffer_size", 0));
00207
00208 socket_->bind(boost::asio::ip::udp::endpoint(multicast_address, port));
00209
00210
00211
00212 socket_->set_option(boost::asio::ip::multicast::join_group(multicast_address), ec);
00213
00214 if (ec != 0)
00215 {
00216 std::cerr << "boost::system::error_code with value " << ec << " was found in attempt to join multicast group" << std::endl;
00217 }
00218 }
00219 }
00220 catch (...)
00221 {
00222 ExceptionHandler(ExceptionHandlerRethrow::yes, "Problem setting up the socket in MulticastTransfer");
00223 }
00224
00225 auto max_subfragments =
00226 static_cast<size_t>(std::ceil(max_fragment_size_words_ / static_cast<float>(subfragment_size_)));
00227
00228 staging_memory_.resize(max_subfragments * (sizeof(subfragment_identifier) + subfragment_size_));
00229
00230 if (TransferInterface::role() == Role::kReceive)
00231 {
00232 book_container_of_buffers(receive_buffers_, max_fragment_size_words_, max_subfragments, 0, max_subfragments - 1);
00233 }
00234
00235 TLOG(TLVL_DEBUG) << GetTraceName() << ": max_subfragments is " << max_subfragments ;
00236 TLOG(TLVL_DEBUG) << GetTraceName() << ": Staging buffer size is " << staging_memory_.size() ;
00237 }
00238
00239 #pragma GCC diagnostic push
00240 #pragma GCC diagnostic ignored "-Wunused-variable"
00241
00242 int artdaq::MulticastTransfer::receiveFragment(artdaq::Fragment& fragment,
00243 size_t receiveTimeout)
00244 {
00245 assert(TransferInterface::role() == Role::kReceive);
00246
00247 if (fragment.dataSizeBytes() > 0)
00248 {
00249 throw cet::exception("MulticastTransfer") << "Error in MulticastTransfer::receiveFragmentFrom: " <<
00250 "nonzero payload found in fragment passed as argument";
00251 }
00252
00253 static bool print_warning = true;
00254
00255 if (print_warning)
00256 {
00257 std::cerr << "Please note that MulticastTransfer::receiveFragmentFrom does not use its receiveTimeout argument" << std::endl;
00258 print_warning = false;
00259 }
00260
00261 fragment.resizeBytes(max_fragment_size_words_ - sizeof(artdaq::detail::RawFragmentHeader));
00262
00263 static auto current_sequenceID = std::numeric_limits<Fragment::sequence_id_t>::max();
00264 static auto current_fragmentID = std::numeric_limits<Fragment::fragment_id_t>::max();
00265
00266 size_t fragment_size = 0;
00267 size_t expected_subfragments = 0;
00268 size_t current_subfragments = 0;
00269 bool fragment_complete = false;
00270 bool last_fragment_truncated = false;
00271
00272 while (true)
00273 {
00274 auto bytes_received = socket_->receive_from(receive_buffers_, *opposite_endpoint_);
00275
00276 size_t bytes_processed = 0;
00277
00278 for (auto& buf : receive_buffers_)
00279 {
00280 auto buf_size = boost::asio::buffer_size(buf);
00281 auto size_t_ptr = boost::asio::buffer_cast<const size_t*>(buf);
00282 auto seqID = *size_t_ptr;
00283 auto fragID = *(size_t_ptr + 1);
00284 auto subfragID = *(size_t_ptr + 2);
00285
00286 if (seqID != current_sequenceID || fragID != current_fragmentID)
00287 {
00288
00289
00290
00291 assert(bytes_processed == 0);
00292
00293 if (current_subfragments < expected_subfragments)
00294 {
00295 last_fragment_truncated = true;
00296
00297 if (expected_subfragments != std::numeric_limits<size_t>::max())
00298 {
00299 std::cerr << "Warning: only received " << current_subfragments << " subfragments for fragment with seqID = " <<
00300 current_sequenceID << ", fragID = " << current_fragmentID << " (expected " << expected_subfragments << ")\n"
00301 << std::endl;
00302 }
00303 else
00304 {
00305 std::cerr << "Warning: only received " << current_subfragments <<
00306 " subfragments for fragment with seqID = " <<
00307 current_sequenceID << ", fragID = " << current_fragmentID <<
00308 ", # of expected subfragments is unknown as fragment header was not received)\n"
00309 << std::endl;
00310 }
00311 }
00312
00313 current_subfragments = 0;
00314 fragment_size = std::numeric_limits<size_t>::max();
00315 expected_subfragments = std::numeric_limits<size_t>::max();
00316 current_sequenceID = seqID;
00317 current_fragmentID = fragID;
00318 }
00319
00320 auto ptr_into_fragment = fragment.headerBeginBytes() + subfragID * subfragment_size_;
00321
00322 auto ptr_into_buffer = boost::asio::buffer_cast<const byte_t*>(buf) + sizeof(subfragment_identifier);
00323
00324 std::copy(ptr_into_buffer, ptr_into_buffer + buf_size - sizeof(subfragment_identifier), ptr_into_fragment);
00325
00326 if (subfragID == 0)
00327 {
00328 if (buf_size >= sizeof(subfragment_identifier) + sizeof(artdaq::detail::RawFragmentHeader))
00329 {
00330 auto payload_size = std::numeric_limits<size_t>::max();
00331 get_fragment_quantities(buf, payload_size, fragment_size, expected_subfragments);
00332
00333 fragment.resizeBytes(payload_size);
00334 }
00335 else
00336 {
00337 throw cet::exception("MulticastTransfer") << "Buffer size is too small to completely contain an artdaq::Fragment header; " <<
00338 "please increase the default size";
00339 }
00340 }
00341
00342 current_subfragments++;
00343
00344 if (current_subfragments == expected_subfragments)
00345 {
00346 fragment_complete = true;
00347 }
00348
00349 bytes_processed += buf_size;
00350
00351 if (bytes_processed >= bytes_received)
00352 {
00353 break;
00354 }
00355 }
00356
00357 if (last_fragment_truncated)
00358 {
00359
00360
00361
00362
00363
00364
00365
00366 assert(!fragment_complete);
00367 TLOG(TLVL_WARNING) << GetTraceName() << ": Got an incomplete fragment" ;
00368 return artdaq::TransferInterface::RECV_TIMEOUT;
00369 }
00370
00371 if (fragment_complete)
00372 {
00373 return source_rank();
00374 }
00375 }
00376
00377 return TransferInterface::RECV_TIMEOUT;
00378 }
00379
00380 #pragma GCC diagnostic pop
00381
00382 int artdaq::MulticastTransfer::receiveFragmentHeader(detail::RawFragmentHeader& header, size_t receiveTimeout)
00383 {
00384 auto ret = receiveFragment(fragment_buffer_, receiveTimeout);
00385 if (ret == source_rank())
00386 {
00387 header = *reinterpret_cast<detail::RawFragmentHeader*>(fragment_buffer_.headerAddress());
00388 return source_rank();
00389 }
00390 return ret;
00391 }
00392
00393 int artdaq::MulticastTransfer::receiveFragmentData(RawDataType* destination, size_t wordCount)
00394 {
00395 if (fragment_buffer_.size() > detail::RawFragmentHeader::num_words()) {
00396 auto dataSize = (fragment_buffer_.size() - detail::RawFragmentHeader::num_words()) * sizeof(RawDataType);
00397 memcpy(destination, fragment_buffer_.headerAddress() + detail::RawFragmentHeader::num_words(), dataSize);
00398 return source_rank();
00399 }
00400 return RECV_TIMEOUT;
00401 }
00402
00403
00404
00405 artdaq::TransferInterface::CopyStatus
00406 artdaq::MulticastTransfer::moveFragment(artdaq::Fragment&& f)
00407 {
00408 return copyFragment(f, 100000000);
00409 }
00410
00411 artdaq::TransferInterface::CopyStatus
00412 artdaq::MulticastTransfer::copyFragment(artdaq::Fragment& fragment,
00413 size_t send_timeout_usec)
00414 {
00415 assert(TransferInterface::role() == Role::kSend);
00416
00417 if (fragment.sizeBytes() > max_fragment_size_words_)
00418 {
00419 throw cet::exception("MulticastTransfer") << "Error in MulticastTransfer::copyFragmentTo: " <<
00420 fragment.sizeBytes() << " byte fragment exceeds max_fragment_size of " << max_fragment_size_words_;
00421 }
00422
00423 static size_t ncalls = 1;
00424 auto num_subfragments = static_cast<size_t>(std::ceil(fragment.sizeBytes() / static_cast<float>(subfragment_size_)));
00425
00426 ncalls++;
00427
00428 fill_staging_memory(fragment);
00429
00430 for (size_t batch_index = 0; ; batch_index++)
00431 {
00432 auto first_subfragment = batch_index * subfragments_per_send_;
00433 auto last_subfragment = (batch_index + 1) * subfragments_per_send_ >= num_subfragments ?
00434 num_subfragments - 1 :
00435 (batch_index + 1) * subfragments_per_send_ - 1;
00436
00437 std::vector<boost::asio::const_buffer> buffers;
00438
00439 book_container_of_buffers(buffers, fragment.sizeBytes(), num_subfragments, first_subfragment, last_subfragment);
00440
00441 socket_->send_to(buffers, *multicast_endpoint_);
00442
00443 usleep(pause_on_copy_usecs_);
00444
00445 if (last_subfragment == num_subfragments - 1)
00446 {
00447 break;
00448 }
00449 }
00450 return CopyStatus::kSuccess;
00451 }
00452
00453 #pragma GCC diagnostic push
00454 #pragma GCC diagnostic ignored "-Wsign-compare"
00455
00456 void artdaq::MulticastTransfer::fill_staging_memory(const artdaq::Fragment& fragment)
00457 {
00458 auto num_subfragments = static_cast<size_t>(std::ceil(fragment.sizeBytes() / static_cast<float>(subfragment_size_)));
00459 TLOG(TLVL_DEBUG) << GetTraceName() << ": # of subfragments to use is " << num_subfragments ;
00460
00461 for (auto i_s = 0; i_s < num_subfragments; ++i_s)
00462 {
00463 auto staging_memory_copyto = &staging_memory_.at(i_s * (sizeof(subfragment_identifier) + subfragment_size_));
00464
00465 subfragment_identifier sfi(fragment.sequenceID(), fragment.fragmentID(), i_s);
00466
00467 std::copy(reinterpret_cast<byte_t*>(&sfi),
00468 reinterpret_cast<byte_t*>(&sfi) + sizeof(subfragment_identifier),
00469 staging_memory_copyto);
00470
00471 auto low_ptr_into_fragment = fragment.headerBeginBytes() + subfragment_size_ * i_s;
00472
00473 auto high_ptr_into_fragment = (i_s == num_subfragments - 1) ?
00474 fragment.dataEndBytes() :
00475 fragment.headerBeginBytes() + subfragment_size_ * (i_s + 1);
00476
00477 std::copy(low_ptr_into_fragment,
00478 high_ptr_into_fragment,
00479 staging_memory_copyto + sizeof(subfragment_identifier));
00480 }
00481 }
00482
00483 #pragma GCC diagnostic pop
00484
00485
00486
00487
00488
00489
00490 template <typename T>
00491 void artdaq::MulticastTransfer::book_container_of_buffers(std::vector<T>& buffers,
00492 const size_t fragment_size,
00493 const size_t total_subfragments,
00494 const size_t first_subfragment_num,
00495 const size_t last_subfragment_num)
00496 {
00497 assert(staging_memory_.size() >= total_subfragments * (sizeof(subfragment_identifier) + subfragment_size_));
00498 assert(buffers.size() == 0);
00499 assert(last_subfragment_num < total_subfragments);
00500
00501 for (auto i_f = first_subfragment_num; i_f <= last_subfragment_num; ++i_f)
00502 {
00503 auto bytes_to_store = (i_f == total_subfragments - 1) ?
00504 sizeof(subfragment_identifier) + (fragment_size - (total_subfragments - 1) * subfragment_size_) :
00505 sizeof(subfragment_identifier) + subfragment_size_;
00506
00507 buffers.emplace_back(&staging_memory_.at(i_f * (sizeof(subfragment_identifier) + subfragment_size_)),
00508 bytes_to_store);
00509 }
00510 }
00511
00512
00513 #pragma GCC diagnostic push // Needed since profile builds will ignore the assert
00514 #pragma GCC diagnostic ignored "-Wunused-variable"
00515
00516 void artdaq::MulticastTransfer::get_fragment_quantities(const boost::asio::mutable_buffer& buf, size_t& payload_size,
00517 size_t& fragment_size,
00518 size_t& expected_subfragments)
00519 {
00520 byte_t* buffer_ptr = boost::asio::buffer_cast<byte_t*>(buf);
00521
00522 auto subfragment_num = *(reinterpret_cast<size_t*>(buffer_ptr) + 2);
00523
00524 assert(subfragment_num == 0);
00525
00526 artdaq::detail::RawFragmentHeader* header =
00527 reinterpret_cast<artdaq::detail::RawFragmentHeader*>(buffer_ptr + sizeof(subfragment_identifier));
00528
00529 fragment_size = header->word_count * sizeof(artdaq::RawDataType);
00530
00531 auto metadata_size = header->metadata_word_count * sizeof(artdaq::RawDataType);
00532 payload_size = fragment_size - metadata_size - artdaq::detail::RawFragmentHeader::num_words() *
00533 sizeof(artdaq::RawDataType);
00534
00535 assert(fragment_size ==
00536 artdaq::detail::RawFragmentHeader::num_words() * sizeof(artdaq::RawDataType) +
00537 metadata_size +
00538 payload_size);
00539
00540 expected_subfragments = static_cast<size_t>(std::ceil(fragment_size / static_cast<float>(subfragment_size_)));
00541 }
00542 #pragma GCC diagnostic pop
00543
00544 void artdaq::MulticastTransfer::set_receive_buffer_size(size_t recv_buff_size)
00545 {
00546 if (recv_buff_size == 0) return;
00547 boost::asio::socket_base::receive_buffer_size actual_recv_buff_size;
00548 socket_->get_option(actual_recv_buff_size);
00549
00550 TLOG(TLVL_DEBUG) << GetTraceName() << ": Receive buffer size is currently " << actual_recv_buff_size.value() <<
00551 " bytes, will try to change it to " << recv_buff_size ;
00552
00553 boost::asio::socket_base::receive_buffer_size recv_buff_option(recv_buff_size);
00554
00555 boost::system::error_code ec;
00556 socket_->set_option(recv_buff_option, ec);
00557
00558 if (ec != 0)
00559 {
00560 std::cerr << "boost::system::error_code with value " << ec <<
00561 " was found in attempt to change receive buffer" << std::endl;
00562 }
00563
00564 socket_->get_option(actual_recv_buff_size);
00565 TLOG(TLVL_DEBUG) << GetTraceName() << ": After attempted change, receive buffer size is now " << actual_recv_buff_size.value() ;
00566 }
00567
00568 #pragma GCC diagnostic pop
00569
00570 DEFINE_ARTDAQ_TRANSFER(artdaq::MulticastTransfer)