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