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