DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
DataHandlingModel.hxx
Go to the documentation of this file.
1// Declarations for DataHandlingModel
2
3#include <folly/coro/BlockingWait.h>
4#include <folly/coro/Timeout.h>
5#include <folly/futures/ThreadWheelTimekeeper.h>
6#include <folly/coro/CurrentExecutor.h>
7#include <folly/CancellationToken.h>
8
9#include <typeinfo>
10
11namespace dunedaq {
12namespace datahandlinglibs {
13
14template<class RDT, class RHT, class LBT, class RPT, class IDT>
15void
17{
18 // Setup request queues
19 //setup_request_queues(mcfg);
20 try {
21 for (auto input : mcfg->get_inputs()) {
22 if (input->get_data_type() == "DataRequest") {
23 m_data_request_receiver = get_iom_receiver<dfmessages::DataRequest>(input->UID()) ;
24 }
25 else {
26 m_raw_data_receiver_connection_name = input->UID();
27 // Parse for prefix
28 std::string conn_name = input->UID();
29 const char delim = '_';
30 std::vector<std::string> words;
31 std::size_t start;
32 std::size_t end = 0;
33 while ((start = conn_name.find_first_not_of(delim, end)) != std::string::npos) {
34 end = conn_name.find(delim, start);
35 words.push_back(conn_name.substr(start, end - start));
36 }
37
38 TLOG_DEBUG(TLVL_WORK_STEPS) << "Initialize connection based on uid: "
39 << m_raw_data_receiver_connection_name << " front word: " << words.front();
40
41 std::string cb_prefix("cb");
42 if (words.front() == cb_prefix) {
43 m_callback_mode = true;
44 }
45
46 if (!m_callback_mode) {
47 m_raw_data_receiver = get_iom_receiver<IDT>(m_raw_data_receiver_connection_name);
48 m_raw_receiver_timeout_ms = std::chrono::milliseconds(input->get_recv_timeout_ms());
49 }
50 }
51 }
52 for (auto output : mcfg->get_outputs()) {
53 if (output->get_data_type() == "TimeSync") {
54 m_generate_timesync = true;
55 m_timesync_sender = get_iom_sender<dfmessages::TimeSync>(output->UID()) ;
56 m_timesync_connection_name = output->UID();
57 break;
58 }
59 }
60 } catch (const ers::Issue& excpt) {
61 throw ResourceQueueError(ERS_HERE, "raw_input or frag_output", "DataHandlingModel", excpt);
62 }
63
64 // Raw input connection sensibility check
65 if (!m_callback_mode && m_raw_data_receiver == nullptr) {
66 ers::error(ConfigurationError(ERS_HERE, m_sourceid, "Non callback mode, and receiver is unset!"));
67 }
68
69 // Instantiate functionalities
70 m_error_registry.reset(new FrameErrorRegistry());
71 m_error_registry->set_ers_metadata("DLH of SourceID[" + std::to_string(mcfg->get_source_id()) + "] ");
72 m_latency_buffer_impl.reset(new LBT());
73 m_raw_processor_impl.reset(new RPT(m_error_registry, mcfg->get_post_processing_enabled()));
74 m_request_handler_impl.reset(new RHT(m_latency_buffer_impl, m_error_registry));
75
76 register_node(mcfg->get_module_configuration()->get_latency_buffer()->UID(), m_latency_buffer_impl);
77 register_node(mcfg->get_module_configuration()->get_data_processor()->UID(), m_raw_processor_impl);
78 register_node(mcfg->get_module_configuration()->get_request_handler()->UID(), m_request_handler_impl);
79
80 //m_request_handler_impl->init(args);
81 //m_raw_processor_impl->init(args);
82 m_request_handler_supports_cutoff_timestamp = m_request_handler_impl->supports_cutoff_timestamp();
83 m_fake_trigger = false;
84 m_raw_receiver_sleep_us = std::chrono::microseconds::zero();
85 m_sourceid.id = mcfg->get_source_id();
86 m_sourceid.subsystem = RDT::subsystem;
87 m_processing_delay_ticks = mcfg->get_module_configuration()->get_post_processing_delay_ticks();
88 m_post_processing_delay_min_wait = mcfg->get_module_configuration()->get_post_processing_delay_min_wait();
89 m_post_processing_delay_max_wait = mcfg->get_module_configuration()->get_post_processing_delay_max_wait();
90
91 if (m_processing_delay_ticks) {
93 ers::error(ConfigurationError(ERS_HERE, m_sourceid,
94 "Delayed postprocessing (post_processing_delay_ticks > 0) requires a sorted buffer (SkipList). "
95 "Queue buffers (FixedRateQueue, BinarySearchQueue) expect in-order data and must use post_processing_delay_ticks = 0."));
96 }
97 }
98
99 // Configure implementations:
100 m_raw_processor_impl->conf(mcfg);
101 // Configure the latency buffer before the request handler so the request handler can check for alignment
102 // restrictions
103 try {
104 m_latency_buffer_impl->conf(mcfg->get_module_configuration()->get_latency_buffer());
105 } catch (const std::bad_alloc& be) {
106 ers::error(ConfigurationError(ERS_HERE, m_sourceid, "Latency Buffer can't be allocated with size!"));
108 m_request_handler_impl->conf(mcfg);
109}
110
111template<class RDT, class RHT, class LBT, class RPT, class IDT>
112void
113DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::conf(const appfwk::DAQModule::CommandData_t& /*args*/)
114{
115 // Register callbacks if operating in that mode.
116 if (m_callback_mode) {
117 // Configure and register consume callback
118 m_consume_callback = std::bind(&DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::consume_callback, this, std::placeholders::_1);
119
120 // Register callback
122 dmcbr->register_callback<IDT>(m_raw_data_receiver_connection_name, m_consume_callback);
123 }
124
125 // Configure threads:
126 m_consumer_thread.set_name("consumer", m_sourceid.id);
127 if (m_generate_timesync) {
128 m_timesync_thread.set_name("timesync", m_sourceid.id);
129 }
130 if (m_processing_delay_ticks) {
131 m_postprocess_scheduler_thread.set_name("pprocsched", m_sourceid.id);
132 m_timekeeper = std::make_unique<folly::ThreadWheelTimekeeper>();
133 }
134}
135
136
137template<class RDT, class RHT, class LBT, class RPT, class IDT>
138void
139DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::start(const appfwk::DAQModule::CommandData_t& args)
140{
141 // Reset opmon variables
142 m_sum_payloads = 0;
143 m_num_payloads = 0;
144 m_sum_requests = 0;
145 m_num_requests = 0;
146 m_num_lb_insert_failures = 0;
147 m_stats_packet_count = 0;
148 m_rawq_timeout_count = 0;
149 m_num_post_processing_delay_max_waits = 0;
150
151 m_t0 = std::chrono::high_resolution_clock::now();
152
153 m_run_number = args.value<dunedaq::daqdataformats::run_number_t>("run", 1);
154
155 TLOG_DEBUG(TLVL_WORK_STEPS) << "Starting threads...";
156 m_raw_processor_impl->start(args);
157 m_request_handler_impl->start(args);
158 if (!m_callback_mode) {
159 m_consumer_thread.set_work(&DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::run_consume, this);
160 }
161 if (m_generate_timesync) {
162 m_timesync_thread.set_work(&DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::run_timesync, this);
163 }
164 if (m_processing_delay_ticks) {
165 m_postprocess_scheduler_thread.set_work(&DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::run_postprocess_scheduler, this);
166 }
167 // Register callback to receive and dispatch data requests
168 m_data_request_receiver->add_callback(
169 std::bind(&DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::dispatch_requests, this, std::placeholders::_1));
170}
171
172template<class RDT, class RHT, class LBT, class RPT, class IDT>
173void
174DataHandlingModel<RDT, RHT, LBT, RPT, IDT>::stop(const appfwk::DAQModule::CommandData_t& args)
175{
176 TLOG_DEBUG(TLVL_WORK_STEPS) << "Stoppping threads...";
177
178 // Stop receiving data requests as first thing
179 m_data_request_receiver->remove_callback();
180 // Stop the other threads
181 m_request_handler_impl->stop(args);
182 if (m_generate_timesync) {
183 while (!m_timesync_thread.get_readiness()) {
184 std::this_thread::sleep_for(std::chrono::milliseconds(10));
185 }
186 }
187 if (!m_callback_mode) {
188 while (!m_consumer_thread.get_readiness()) {
189 std::this_thread::sleep_for(std::chrono::milliseconds(10));
190 }
191 }
192 if (m_processing_delay_ticks) {
193 m_baton.post(); // In case the coroutine is still waiting when the consumer has stopped
194 while (!m_postprocess_scheduler_thread.get_readiness()) {
195 std::this_thread::sleep_for(std::chrono::milliseconds(10));
196 }
197 }
198 TLOG_DEBUG(TLVL_WORK_STEPS) << "Flushing latency buffer with occupancy: " << m_latency_buffer_impl->occupancy();
199 m_latency_buffer_impl->flush();
200 m_raw_processor_impl->stop(args);
201 m_raw_processor_impl->reset_last_daq_time();
202}
203
204template<class RDT, class RHT, class LBT, class RPT, class IDT>
205void
207 {
209 ri.set_sum_payloads(m_sum_payloads.load());
210 ri.set_num_payloads(m_num_payloads.exchange(0));
211
212 ri.set_num_data_input_timeouts(m_rawq_timeout_count.exchange(0));
213
214 auto now = std::chrono::high_resolution_clock::now();
215 int new_packets = m_stats_packet_count.exchange(0);
216 double seconds = std::chrono::duration_cast<std::chrono::microseconds>(now - m_t0).count() / 1000000.;
217 m_t0 = now;
218
219 // 08-May-2025, KAB: added a message to warn users when latency buffer inserts are failing.
220 int local_num_lb_insert_failures = m_num_lb_insert_failures.exchange(0);
221 if (local_num_lb_insert_failures != 0) {
222 ers::warning(NonZeroLatencyBufferInsertFailures(ERS_HERE, m_sourceid, local_num_lb_insert_failures, ri.num_payloads()));
223 }
224
225 ri.set_rate_payloads_consumed(new_packets / seconds / 1000.);
226 ri.set_num_lb_insert_failures(local_num_lb_insert_failures);
227 ri.set_sum_requests(m_sum_requests.load());
228 ri.set_num_requests(m_num_requests.exchange(0));
229 ri.set_num_post_processing_delay_max_waits(m_num_post_processing_delay_max_waits.exchange(0));
230 ri.set_last_daq_timestamp(m_raw_processor_impl->get_last_daq_time());
231 ri.set_newest_timestamp(m_raw_processor_impl->get_last_daq_time());
232 ri.set_oldest_timestamp(m_request_handler_impl->get_oldest_time());
233
234 this->publish(std::move(ri));
235 }
236
237template<class RDT, class RHT, class LBT, class RPT, class IDT>
238void
240 if constexpr (std::is_same_v<IDT, RDT>) {
241 process_item(std::move(payload));
242 } else {
243 auto transformed = transform_payload(payload);
244 for (auto& i : transformed) {
245 process_item(std::move(i));
246 }
247 }
248}
249
250template<class RDT, class RHT, class LBT, class RPT, class IDT>
251void
253 transform_and_process(std::move(payload));
254}
255
256template<class RDT, class RHT, class LBT, class RPT, class IDT>
257void
259{
260 m_raw_processor_impl->preprocess_item(&payload);
261 if (m_request_handler_supports_cutoff_timestamp) {
262 int64_t diff1 = payload.get_timestamp() - m_request_handler_impl->get_cutoff_timestamp();
263 if (diff1 <= 0) {
264 //m_request_handler_impl->increment_tardy_tp_count();
265 ers::warning(DataPacketArrivedTooLate(ERS_HERE, m_sourceid, m_run_number, payload.get_timestamp(),
266 m_request_handler_impl->get_cutoff_timestamp(), diff1,
267 (static_cast<double>(diff1)/62500.0)));
268 }
269 }
270 if (!m_latency_buffer_impl->write(std::move(payload))) {
271 // TLOG_DEBUG(TLVL_TAKE_NOTE) << "***ERROR: Latency buffer insert failed! (Payload timestamp=" << payload.get_timestamp() << ")";
272 m_num_lb_insert_failures++;
273 return;
274 }
275
276 if (m_processing_delay_ticks == 0) {
277 m_raw_processor_impl->postprocess_item(m_latency_buffer_impl->back());
278 ++m_num_payloads;
279 ++m_sum_payloads;
280 ++m_stats_packet_count;
281 } else {
282 m_baton.post();
283 }
284}
286template<class RDT, class RHT, class LBT, class RPT, class IDT>
287void
289{
290 folly::coro::blockingWait(postprocess_schedule());
292
293template<class RDT, class RHT, class LBT, class RPT, class IDT>
294void
296{
298 TLOG_DEBUG(TLVL_WORK_STEPS) << "Consumer thread started...";
299 m_rawq_timeout_count = 0;
300 m_num_payloads = 0;
301 m_sum_payloads = 0;
302 m_stats_packet_count = 0;
303 m_num_post_processing_delay_max_waits = 0;
304
305 while (m_run_marker.load()) {
306 // Try to acquire data
307
308 auto opt_payload = m_raw_data_receiver->try_receive(m_raw_receiver_timeout_ms);
309
310 if (opt_payload) {
311 IDT& payload = opt_payload.value();
312 transform_and_process(std::move(payload));
313 } else {
314 ++m_rawq_timeout_count;
315 // Protection against a zero sleep becoming a yield
316 if ( m_raw_receiver_sleep_us != std::chrono::microseconds::zero())
317 std::this_thread::sleep_for(m_raw_receiver_sleep_us);
319 }
320 TLOG_DEBUG(TLVL_WORK_STEPS) << "Consumer thread joins... ";
321}
322
323template<class RDT, class RHT, class LBT, class RPT, class IDT>
324folly::coro::Task<void>
326{
327
328 TLOG_DEBUG(TLVL_WORK_STEPS) << "Postprocess schedule coroutine started...";
329 TLOG() << "***** Starting post-process coroutine with timout " << m_post_processing_delay_max_wait << " *****";
330
331
332 PostprocessScheduleAlgorithm sched_algo{ *m_latency_buffer_impl,
333 *m_raw_processor_impl,
334 m_processing_delay_ticks,
335 m_post_processing_delay_min_wait,
336 m_post_processing_delay_max_wait };
337
338 const auto wait_data = [this]() -> folly::coro::Task<void> {
339 // folly::coro::timeout cancels the task on timeout.
340 // Baton is not cancellable, so we attach a callback to resume the coroutine.
341 auto token = co_await folly::coro::co_current_cancellation_token;
342 folly::CancellationCallback cb(token, [this] { m_baton.post(); });
343 co_await m_baton; // Wait data
344 };
345
346 while (m_run_marker.load()) {
347 bool timeout = false;
348
349 if ( m_post_processing_delay_max_wait > 0 ) {
350 try {
351 co_await folly::coro::timeout(
352 wait_data(),
353 std::chrono::milliseconds{ m_post_processing_delay_max_wait },
354 m_timekeeper.get());
355
356 } catch (const folly::FutureTimeout&) {
357 timeout = true;
358 ++m_num_post_processing_delay_max_waits;
359 }
360 } else {
361 co_await m_baton;
362 }
363
364 m_baton.reset();
365
366 if (auto processed = sched_algo.run(timeout); processed > 0) {
367 m_num_payloads += processed;
368 m_sum_payloads += processed;
369 m_stats_packet_count += processed;
370
371 }
372 }
373}
374
375template<class RDT, class RHT, class LBT, class RPT, class IDT>
376void
378{
379 TLOG_DEBUG(TLVL_WORK_STEPS) << "TimeSync thread started...";
380 m_num_requests = 0;
381 m_sum_requests = 0;
382 uint64_t msg_seqno = 0;
383 timestamp_t prev_timestamp = 0;
384 auto once_per_run = true;
385 size_t zero_timestamp_count = 0;
386 size_t duplicate_timestamp_count = 0;
387 size_t total_timestamp_count = 0;
388 while (m_run_marker.load()) {
389 try {
390 auto timesyncmsg = dfmessages::TimeSync(m_raw_processor_impl->get_last_daq_time());
391 ++total_timestamp_count;
392 // daq_time is zero for the first received timesync, and may
393 // be the same as the previous daq_time if the data has
394 // stopped flowing. In both cases we don't send the TimeSync
395 if (timesyncmsg.daq_time != 0 && timesyncmsg.daq_time != prev_timestamp) {
396 prev_timestamp = timesyncmsg.daq_time;
397 timesyncmsg.run_number = m_run_number;
398 timesyncmsg.sequence_number = ++msg_seqno;
399 timesyncmsg.source_id = m_sourceid.id;
400 TLOG_DEBUG(TLVL_TIME_SYNCS) << "New timesync: daq=" << timesyncmsg.daq_time
401 << " wall=" << timesyncmsg.system_time << " run=" << timesyncmsg.run_number
402 << " seqno=" << timesyncmsg.sequence_number << " source_id=" << timesyncmsg.source_id;
403 try {
404 dfmessages::TimeSync timesyncmsg_copy(timesyncmsg);
405 m_timesync_sender->send(std::move(timesyncmsg_copy), std::chrono::milliseconds(500));
406 } catch (ers::Issue& excpt) {
408 TimeSyncTransmissionFailed(ERS_HERE, m_sourceid, m_timesync_connection_name, excpt));
409 }
410
411 if (m_fake_trigger) {
413 ++m_current_fake_trigger_id;
414 dr.trigger_number = m_current_fake_trigger_id;
415 dr.trigger_timestamp = timesyncmsg.daq_time > 500 * us ? timesyncmsg.daq_time - 500 * us : 0;
416 auto width = 300000;
417 uint offset = 100;
420 dr.request_information.component = m_sourceid;
421 dr.data_destination = "data_fragments_q";
422 TLOG_DEBUG(TLVL_WORK_STEPS) << "Issuing fake trigger based on timesync. "
423 << " ts=" << dr.trigger_timestamp
424 << " window_begin=" << dr.request_information.window_begin
425 << " window_end=" << dr.request_information.window_end;
426 m_request_handler_impl->issue_request(dr);
427
428 ++m_num_requests;
429 ++m_sum_requests;
430 }
431 } else {
432 if (timesyncmsg.daq_time == 0) {++zero_timestamp_count;}
433 if (timesyncmsg.daq_time == prev_timestamp) {++duplicate_timestamp_count;}
434 if (once_per_run) {
435 TLOG() << "Timesync with DAQ time 0 won't be sent out as it's an invalid sync.";
436 once_per_run = false;
437 }
438 }
439 } catch (const iomanager::TimeoutExpired& excpt) {
440 // ++m_timesyncqueue_timeout;
441 }
442 // Split up the 100ms sleep into 10 sleeps of 10ms, so we respond to "stop" quicker
443 for (size_t i=0; i<10; ++i) {
444 std::this_thread::sleep_for(std::chrono::milliseconds(10));
445 if (!m_run_marker.load()) {
446 break;
447 }
448 }
449 }
450 once_per_run = true;
451 TLOG_DEBUG(TLVL_WORK_STEPS) << "TimeSync thread joins... (timestamp count, zero/same/total = "
452 << zero_timestamp_count << "/" << duplicate_timestamp_count << "/"
453 << total_timestamp_count << ")";
454}
455
456template<class RDT, class RHT, class LBT, class RPT, class IDT>
457void
459{
460 if (data_request.request_information.component != m_sourceid) {
461 ers::error(RequestSourceIDMismatch(ERS_HERE, m_sourceid, data_request.request_information.component));
462 return;
463 }
464 TLOG_DEBUG(TLVL_QUEUE_POP) << "Received DataRequest"
465 << " for trig/seq_number " << data_request.trigger_number << "." << data_request.sequence_number
466 << ", runno " << data_request.run_number
467 << ", trig timestamp " << data_request.trigger_timestamp
468 << ", SourceID: " << data_request.request_information.component
469 << ", window begin/end " << data_request.request_information.window_begin
470 << "/" << data_request.request_information.window_end
471 << ", dest: " << data_request.data_destination;
472 m_request_handler_impl->issue_request(data_request);
473 ++m_num_requests;
474 ++m_sum_requests;
475}
476
477} // namespace datahandlinglibs
478} // namespace dunedaq
#define ERS_HERE
const dunedaq::appmodel::LatencyBuffer * get_latency_buffer() const
Get "latency_buffer" relationship value.
uint64_t get_post_processing_delay_ticks() const
Get "post_processing_delay_ticks" attribute value. Number of clock tick by which post processing of i...
const dunedaq::appmodel::DataProcessor * get_data_processor() const
Get "data_processor" relationship value.
uint64_t get_post_processing_delay_max_wait() const
Get "post_processing_delay_max_wait" attribute value. Maximum wait time (ms) before post processing c...
uint64_t get_post_processing_delay_min_wait() const
Get "post_processing_delay_min_wait" attribute value. Minimum time (ms) between consecutive post proc...
const dunedaq::appmodel::RequestHandler * get_request_handler() const
Get "request_handler" relationship value.
const dunedaq::appmodel::DataHandlerConf * get_module_configuration() const
Get "module_configuration" relationship value.
uint32_t get_source_id() const
Get "source_id" attribute value.
bool get_post_processing_enabled() const
Get "post_processing_enabled" attribute value.
const std::string & UID() const noexcept
const std::vector< const dunedaq::confmodel::Connection * > & get_inputs() const
Get "inputs" relationship value. List of connections to/from this module.
const std::vector< const dunedaq::confmodel::Connection * > & get_outputs() const
Get "outputs" relationship value. Output connections from this module.
void init(const appmodel::DataHandlerModule *modconf)
Forward calls from the appfwk.
void stop(const appfwk::DAQModule::CommandData_t &args)
void start(const appfwk::DAQModule::CommandData_t &args)
void conf(const appfwk::DAQModule::CommandData_t &args)
void dispatch_requests(dfmessages::DataRequest &data_request)
void run_consume()
Function that will be run in its own thread to read the raw packets from the connection and add them ...
void run_timesync()
Function that will be run in its own thread and sends periodic timesync messages by pushing them to t...
static std::shared_ptr< DataMoveCallbackRegistry > get()
Base class for any user define issue.
Definition Issue.hpp:69
double offset
static int64_t now()
#define TLOG_DEBUG(lvl,...)
Definition Logging.hpp:112
#define TLOG(...)
Definition macro.hpp:22
uint32_t run_number_t
Type used to represent run number.
Definition Types.hpp:20
The DUNE-DAQ namespace.
static std::shared_ptr< iomanager::SenderConcept< Datatype > > get_iom_sender(iomanager::ConnectionId const &id)
SourceID[" << sourceid << "] Command daqdataformats::SourceID Readout Initialization std::string initerror Configuration std::string conferror Configuration std::string conferror TimeSyncTransmissionFailed
static std::shared_ptr< iomanager::ReceiverConcept< Datatype > > get_iom_receiver(iomanager::ConnectionId const &id)
void warning(const Issue &issue)
Definition ers.hpp:115
void error(const Issue &issue)
Definition ers.hpp:81
timestamp_t window_end
End of the data collection window.
SourceID component
The Requested Component.
timestamp_t window_begin
Start of the data collection window.
This message represents a request for data sent to a single component of the DAQ.
sequence_number_t sequence_number
Sequence Number of the request.
trigger_number_t trigger_number
Trigger number the request corresponds to.
timestamp_t trigger_timestamp
Timestamp of trigger.
run_number_t run_number
The current run number.
A synthetic message used to ensure that all elements of a DAQ system are synchronized.
Definition TimeSync.hpp:25