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