Project
Loading...
Searching...
No Matches
DataRelayer.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
16
22#include "Framework/DataRef.h"
24#include "Framework/InputSpan.h"
26#include "Framework/Logger.h"
27#include "Framework/PartRef.h"
33#include "DataRelayerHelpers.h"
34#include "InputRouteHelpers.h"
42
45
46#include <Monitoring/Metric.h>
47#include <Monitoring/Monitoring.h>
48
49#include <fairlogger/Logger.h>
50#include <fairmq/Channel.h>
51#include <functional>
52#include <fairmq/shmem/Message.h>
53#include <fairmq/Device.h>
54#include <fmt/format.h>
55#include <fmt/ostream.h>
56#include <span>
57#include <string>
58
59using namespace o2::framework::data_matcher;
62using Verbosity = o2::monitoring::Verbosity;
63
65// Stream which keeps track of the calibration lifetime logic
67
68namespace o2::framework
69{
70
71constexpr int INVALID_INPUT = -1;
72
74 std::vector<InputRoute> const& routes,
76 ServiceRegistryRef services,
77 int pipelineLength)
78 : mContext{services},
79 mTimesliceIndex{index},
80 mCompletionPolicy{policy},
81 mDistinctRoutesIndex{DataRelayerHelpers::createDistinctRouteIndex(routes)},
82 mInputMatchers{DataRelayerHelpers::createInputMatchers(routes)},
83 mMaxLanes{InputRouteHelpers::maxLanes(routes)}
84{
85 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
86
87 if (policy.configureRelayer == nullptr) {
88 if (pipelineLength == -1) {
89 auto getPipelineLengthHelper = [&services]() {
90 try {
91 return DefaultsHelpers::pipelineLength(*services.get<RawDeviceService>().device()->fConfig);
92 } catch (...) {
94 }
95 };
96 static int detectedPipelineLength = getPipelineLengthHelper();
97 pipelineLength = detectedPipelineLength;
98 }
99 setPipelineLength(pipelineLength);
100 } else {
101 policy.configureRelayer(*this);
102 }
103
104 // The queries are all the same, so we only have width 1
105 auto numInputTypes = mDistinctRoutesIndex.size();
106 auto& states = services.get<DataProcessingStates>();
107 std::string queries = "";
108 for (short i = 0; i < numInputTypes; ++i) {
109 char buffer[128];
110 assert(mDistinctRoutesIndex[i] < routes.size());
111 mInputs.push_back(routes[mDistinctRoutesIndex[i]].matcher);
112 auto& matcher = routes[mDistinctRoutesIndex[i]].matcher;
113 DataSpecUtils::describe(buffer, 127, matcher);
114 queries += std::string_view(buffer, strlen(buffer));
115 queries += ";";
116 }
117 auto stateId = (short)ProcessingStateId::DATA_QUERIES;
118 states.registerState({.name = "data_queries", .stateId = stateId, .sendInitialValue = true, .defaultEnabled = true});
119 states.updateState(DataProcessingStates::CommandSpec{.id = stateId, .size = (int)queries.size(), .data = queries.data()});
120 states.processCommandQueue();
121}
122
124{
125 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
126 auto& variables = mTimesliceIndex.getVariablesForSlot(slot);
127 return VariableContextHelpers::getTimeslice(variables);
128}
129
130DataRelayer::ActivityStats DataRelayer::processDanglingInputs(std::vector<ExpirationHandler> const& expirationHandlers,
131 ServiceRegistryRef services, bool createNew)
132{
133 LOGP(debug, "DataRelayer::processDanglingInputs");
134 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
135 auto& deviceProxy = services.get<FairMQDeviceProxy>();
136
137 ActivityStats activity;
139 if (expirationHandlers.empty()) {
140 LOGP(debug, "DataRelayer::processDanglingInputs: No expiration handlers");
141 return activity;
142 }
143 // Create any slot for the time based fields
144 std::vector<TimesliceSlot> slotsCreatedByHandlers;
145 if (createNew) {
146 LOGP(debug, "Creating new slot");
147 for (auto& handler : expirationHandlers) {
148 LOGP(debug, "handler.creator for {}", handler.name);
149 auto channelIndex = deviceProxy.getInputChannelIndex(handler.routeIndex);
150 slotsCreatedByHandlers.push_back(handler.creator(services, channelIndex));
151 }
152 }
153 // Count how many slots are not invalid
154 auto validSlots = 0;
155 for (auto slot : slotsCreatedByHandlers) {
156 if (slot.index == TimesliceSlot::INVALID) {
157 continue;
158 }
159 validSlots++;
160 }
161 if (validSlots > 0) {
162 activity.newSlots++;
163 LOGP(debug, "DataRelayer::processDanglingInputs: {} slots created by handler", validSlots);
164 } else {
165 LOGP(debug, "DataRelayer::processDanglingInputs: no slots created by handler");
166 }
167 // Outer loop, we process all the records because the fact that the record
168 // expires is independent from having received data for it.
169 int headerPresent = 0;
170 int payloadPresent = 0;
171 int noCheckers = 0;
172 int badSlot = 0;
173 int checkerDenied = 0;
174 for (size_t ti = 0; ti < mTimesliceIndex.size(); ++ti) {
175 TimesliceSlot slot{ti};
176 if (mTimesliceIndex.isValid(slot) == false) {
177 continue;
178 }
179 assert(mDistinctRoutesIndex.empty() == false);
180 auto& variables = mTimesliceIndex.getVariablesForSlot(slot);
181 auto timestamp = VariableContextHelpers::getTimeslice(variables);
182 // We iterate on all the hanlders checking if they need to be expired.
183 for (size_t ei = 0; ei < expirationHandlers.size(); ++ei) {
184 auto& expirator = expirationHandlers[ei];
185 // We check that no data is already there for the given cell
186 // it is enough to check the first element
187 auto& part = mCache[ti * mDistinctRoutesIndex.size() + expirator.routeIndex.value];
188 if (!part.empty() && (part | get_header{0}) != nullptr) {
189 headerPresent++;
190 continue;
191 }
192 if (!part.empty() && (part | get_payload{0, 0}) != nullptr) {
193 payloadPresent++;
194 continue;
195 }
196 // We check that the cell can actually be expired.
197 if (!expirator.checker) {
198 noCheckers++;
199 continue;
200 }
201 if (slotsCreatedByHandlers[ei] != slot) {
202 badSlot++;
203 continue;
204 }
205
206 auto getPartialRecord = [&cache = mCache, numInputTypes = mDistinctRoutesIndex.size()](int li) -> std::span<std::vector<fair::mq::MessagePtr> const> {
207 auto offset = li * numInputTypes;
208 assert(cache.size() >= offset + numInputTypes);
209 auto const start = cache.data() + offset;
210 auto const end = cache.data() + offset + numInputTypes;
211 return {start, end};
212 };
213
214 auto partial = getPartialRecord(ti);
215 auto nPartsGetter = [&partial](size_t idx) {
216 return partial[idx] | count_parts{};
217 };
218 auto refCountGetter = [&partial](size_t idx) -> int {
219 auto& header = static_cast<const fair::mq::shmem::Message&>(*(partial[idx] | get_header{0}));
220 return header.GetRefCount();
221 };
222 auto indicesGetter = [&partial](size_t idx, DataRefIndices indices) -> DataRef {
223 if (!partial[idx].empty()) {
224 auto const& headerMsg = partial[idx][indices.headerIdx];
225 auto const& payloadMsg = partial[idx][indices.payloadIdx];
226 if (headerMsg) {
227 return DataRef{nullptr,
228 reinterpret_cast<const char*>(headerMsg->GetData()),
229 payloadMsg ? reinterpret_cast<char const*>(payloadMsg->GetData()) : nullptr,
230 payloadMsg ? payloadMsg->GetSize() : 0};
231 }
232 }
233 return DataRef{};
234 };
235 auto nextIndicesGetter = [&partial](size_t idx, DataRefIndices current) -> DataRefIndices {
236 auto next = partial[idx] | get_next_pair{current};
237 return next.headerIdx < partial[idx].size() ? next : DataRefIndices{size_t(-1), size_t(-1)};
238 };
239 auto payloadGetter = [&partial](size_t idx, DataRefIndices current) -> fair::mq::Message* {
240 auto const& msgs = partial[idx];
241 if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) {
242 return nullptr;
243 }
244 return msgs[current.payloadIdx].get();
245 };
246 InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, static_cast<size_t>(partial.size())};
247 // Setup the input span
248
249 if (expirator.checker(services, timestamp.value, span) == false) {
250 checkerDenied++;
251 continue;
252 }
253
254 assert(ti * mDistinctRoutesIndex.size() + expirator.routeIndex.value < mCache.size());
255 assert(expirator.handler);
256 PartRef newRef;
257 expirator.handler(services, newRef, variables);
258 part.clear();
259 part.emplace_back(std::move(newRef.header));
260 part.emplace_back(std::move(newRef.payload));
261 activity.expiredSlots++;
262
263 mTimesliceIndex.markAsDirty(slot, true);
264 assert((part | get_header{0}) != nullptr);
265 assert((part | get_payload{0, 0}) != nullptr);
266 }
267 }
268 LOGP(debug, "DataRelayer::processDanglingInputs headerPresent:{}, payloadPresent:{}, noCheckers:{}, badSlot:{}, checkerDenied:{}",
269 headerPresent, payloadPresent, noCheckers, badSlot, checkerDenied);
270 return activity;
271}
272
276size_t matchToContext(void const* data,
277 std::vector<DataDescriptorMatcher> const& matchers,
278 std::vector<size_t> const& index,
279 VariableContext& context)
280{
281 for (size_t ri = 0, re = index.size(); ri < re; ++ri) {
282 auto& matcher = matchers[index[ri]];
283
284 if (matcher.match(reinterpret_cast<char const*>(data), context)) {
285 context.commit();
286 return ri;
287 }
288 context.discard();
289 }
290 return INVALID_INPUT;
291}
292
296{
297 static const std::string nullstring{"null"};
298
299 context.publish([](VariableContext const& variables, TimesliceSlot slot, void* context) {
300 auto& states = *static_cast<DataProcessingStates*>(context);
301 static std::string state = "";
302 state.clear();
303 for (size_t i = 0; i < MAX_MATCHING_VARIABLE; ++i) {
304 auto var = variables.get(i);
305 if (auto pval = std::get_if<uint64_t>(&var)) {
306 state += std::to_string(*pval);
307 } else if (auto pval = std::get_if<uint32_t>(&var)) {
308 state += std::to_string(*pval);
309 } else if (auto pval2 = std::get_if<std::string>(&var)) {
310 state += *pval2;
311 } else {
312 }
313 state += ";";
314 }
315 states.updateState({.id = short((int)ProcessingStateId::CONTEXT_VARIABLES_BASE + slot.index),
316 .size = (int)state.size(),
317 .data = state.data()});
318 },
319 &states, slot);
320}
321
323{
324 auto newOldest = mTimesliceIndex.setOldestPossibleInput(proposed, channel);
325 LOGP(debug, "DataRelayer::setOldestPossibleInput {} from channel {}", newOldest.timeslice.value, newOldest.channel.value);
326 static bool dontDrop = getenv("DPL_DONT_DROP_OLD_TIMESLICE") && atoi(getenv("DPL_DONT_DROP_OLD_TIMESLICE"));
327 if (dontDrop) {
328 return;
329 }
330 for (size_t si = 0; si < mCache.size() / mInputs.size(); ++si) {
331 auto& variables = mTimesliceIndex.getVariablesForSlot({si});
332 auto timestamp = VariableContextHelpers::getTimeslice(variables);
333 auto valid = mTimesliceIndex.validateSlot({si}, newOldest.timeslice);
334 if (valid) {
335 if (mTimesliceIndex.isValid({si})) {
336 LOGP(debug, "Keeping slot {} because data has timestamp {} while oldest possible timestamp is {}", si, timestamp.value, newOldest.timeslice.value);
337 }
338 continue;
339 }
340 mPruneOps.push_back(PruneOp{si});
341 bool didDrop = false;
342 for (size_t mi = 0; mi < mInputs.size(); ++mi) {
343 auto& input = mInputs[mi];
344 auto& element = mCache[si * mInputs.size() + mi];
345 if (!element.empty()) {
346 if (input.lifetime != Lifetime::Condition && mCompletionPolicy.name != "internal-dpl-injected-dummy-sink") {
347 didDrop = true;
348 auto& state = mContext.get<DeviceState>();
350 LOGP(warning, "Stop transition requested. Dropping incomplete {} Lifetime::{} data in slot {} with timestamp {} < {} as it will never be completed.", DataSpecUtils::describe(input), input.lifetime, si, timestamp.value, newOldest.timeslice.value);
351 } else {
352 LOGP(error, "Dropping incomplete {} Lifetime::{} data in slot {} with timestamp {} < {} as it can never be completed.", DataSpecUtils::describe(input), input.lifetime, si, timestamp.value, newOldest.timeslice.value);
353 }
354 } else {
355 LOGP(debug,
356 "Silently dropping data {} in pipeline slot {} because it has timeslice {} < {} after receiving data from channel {}."
357 "Because Lifetime::Timeframe data not there and not expected (e.g. due to sampling) we drop non sampled, non timeframe data (e.g. Conditions).",
358 DataSpecUtils::describe(input), si, timestamp.value, newOldest.timeslice.value,
359 mTimesliceIndex.getChannelInfo(channel).channel->GetName());
360 }
361 }
362 }
363 // We did drop some data. Let's print what was missing.
364 if (didDrop) {
365 for (size_t mi = 0; mi < mInputs.size(); ++mi) {
366 auto& input = mInputs[mi];
367 if (input.lifetime == Lifetime::Timer) {
368 continue;
369 }
370 auto& element = mCache[si * mInputs.size() + mi];
371 if (element.empty()) {
372 auto& state = mContext.get<DeviceState>();
374 if (state.allowedProcessing == DeviceState::CalibrationOnly) {
375 O2_SIGNPOST_ID_GENERATE(cid, calibration);
376 O2_SIGNPOST_EVENT_EMIT(calibration, cid, "expected_missing_data", "Expected missing %{public}s (lifetime:%d) while dropping non-calibration data in slot %zu with timestamp %zu < %zu.",
377 DataSpecUtils::describe(input).c_str(), (int)input.lifetime, si, timestamp.value, newOldest.timeslice.value);
378 } else {
379 LOGP(info, "Missing {} (lifetime:{}) while dropping incomplete data in slot {} with timestamp {} < {}.", DataSpecUtils::describe(input), input.lifetime, si, timestamp.value, newOldest.timeslice.value);
380 }
381 } else {
382 if (state.allowedProcessing == DeviceState::CalibrationOnly) {
383 O2_SIGNPOST_ID_GENERATE(cid, calibration);
384 O2_SIGNPOST_EVENT_EMIT_INFO(calibration, cid, "expected_missing_data", "Not processing in calibration mode: missing %s (lifetime:%d) while dropping incomplete data in slot %zu with timestamp %zu < %zu.",
385 DataSpecUtils::describe(input).c_str(), (int)input.lifetime, si, timestamp.value, newOldest.timeslice.value);
386 } else {
387 LOGP(error, "Missing {} (lifetime:{}) while dropping incomplete data in slot {} with timestamp {} < {}.", DataSpecUtils::describe(input), input.lifetime, si, timestamp.value, newOldest.timeslice.value);
388 }
389 }
390 }
391 }
392 }
393 }
394}
395
400
402{
403 for (auto& op : mPruneOps) {
404 this->pruneCache(op.slot, onDrop);
405 }
406 mPruneOps.clear();
407}
408
410{
411 // We need to prune the cache from the old stuff, if any. Otherwise we
412 // simply store the payload in the cache and we mark relevant bit in the
413 // hence the first if.
414 auto pruneCache = [&onDrop,
415 &cache = mCache,
416 &cachedStateMetrics = mCachedStateMetrics,
417 numInputTypes = mDistinctRoutesIndex.size(),
418 &index = mTimesliceIndex,
419 ref = mContext](TimesliceSlot slot) {
420 if (onDrop) {
421 auto oldestPossibleTimeslice = index.getOldestPossibleOutput();
422 // State of the computation
423 std::vector<std::vector<fair::mq::MessagePtr>> dropped(numInputTypes);
424 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
425 auto cacheId = slot.index * numInputTypes + ai;
426 cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING;
427 // TODO: in the original implementation of the cache, there have been only two messages per entry,
428 // check if the 2 above corresponds to the number of messages.
429 if (!cache[cacheId].empty()) {
430 dropped[ai] = std::move(cache[cacheId]);
431 }
432 }
433 bool anyDropped = std::any_of(dropped.begin(), dropped.end(), [](auto& m) { return !m.empty(); });
434 if (anyDropped) {
435 O2_SIGNPOST_ID_GENERATE(aid, data_relayer);
436 O2_SIGNPOST_EVENT_EMIT(data_relayer, aid, "pruneCache", "Dropping stuff from slot %zu with timeslice %zu", slot.index, oldestPossibleTimeslice.timeslice.value);
437 std::vector<std::span<fair::mq::MessagePtr>> droppedSpans(dropped.size());
438 for (size_t ai = 0, ae = dropped.size(); ai != ae; ++ai) {
439 droppedSpans[ai] = dropped[ai];
440 }
441 onDrop(slot, droppedSpans, oldestPossibleTimeslice);
442 }
443 }
444 assert(cache.empty() == false);
445 assert(index.size() * numInputTypes == cache.size());
446 // Prune old stuff from the cache, hopefully deleting it...
447 // We set the current slot to the timeslice value, so that old stuff
448 // will be ignored.
449 assert(numInputTypes * slot.index < cache.size());
450 for (size_t ai = slot.index * numInputTypes, ae = ai + numInputTypes; ai != ae; ++ai) {
451 cache[ai].clear();
452 cachedStateMetrics[ai] = CacheEntryStatus::EMPTY;
453 }
454 };
455
456 pruneCache(slot);
457}
458
459bool isCalibrationData(std::unique_ptr<fair::mq::Message>& first)
460{
461 auto* dph = o2::header::get<DataProcessingHeader*>(first->GetData());
463}
464
466 DataRelayer::relay(void const* rawHeader,
467 std::unique_ptr<fair::mq::Message>* messages,
468 InputInfo const& info,
469 size_t nMessages,
470 size_t nPayloads,
471 OnInsertionCallback onInsertion,
472 OnDropCallback onDrop)
473{
474 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
475 DataProcessingHeader const* dph = o2::header::get<DataProcessingHeader*>(rawHeader);
476 // IMPLEMENTATION DETAILS
477 //
478 // This returns true if a given slot is available for the current number of lanes
479 auto isSlotInLane = [currentLane = dph->startTime, maxLanes = mMaxLanes](TimesliceSlot slot) {
480 return (slot.index % maxLanes) == (currentLane % maxLanes);
481 };
482 // This returns the identifier for the given input. We use a separate
483 // function because while it's trivial now, the actual matchmaking will
484 // become more complicated when we will start supporting ranges.
485 auto getInputTimeslice = [&matchers = mInputMatchers,
486 &distinctRoutes = mDistinctRoutesIndex,
487 &rawHeader,
488 &index = mTimesliceIndex](VariableContext& context)
489 -> std::tuple<int, TimesliceId> {
492 auto input = matchToContext(rawHeader, matchers, distinctRoutes, context);
493
494 if (input == INVALID_INPUT) {
495 return {
498 };
499 }
502 if (auto pval = std::get_if<uint64_t>(&context.get(0))) {
503 TimesliceId timeslice{*pval};
504 return {input, timeslice};
505 }
506 // If we get here it means we need to push something out of the cache.
507 return {
510 };
511 };
512
513 // Actually save the header / payload in the slot
514 auto saveInSlot = [&cachedStateMetrics = mCachedStateMetrics,
515 &messages,
516 &nMessages,
517 &nPayloads,
518 &onInsertion,
519 &cache = mCache,
520 &services = mContext,
521 numInputTypes = mDistinctRoutesIndex.size()](TimesliceId timeslice, int input, TimesliceSlot slot, InputInfo const& info) -> size_t {
522 O2_SIGNPOST_ID_GENERATE(aid, data_relayer);
523 O2_SIGNPOST_EVENT_EMIT(data_relayer, aid, "saveInSlot", "saving %{public}s@%zu in slot %zu from %{public}s",
524 fmt::format("{:x}", *o2::header::get<DataHeader*>(messages[0]->GetData())).c_str(),
525 timeslice.value, slot.index,
526 info.index.value == ChannelIndex::INVALID ? "invalid" : services.get<FairMQDeviceProxy>().getInputChannel(info.index)->GetName().c_str());
527 auto cacheIdx = numInputTypes * slot.index + input;
528 auto& target = cache[cacheIdx];
529 cachedStateMetrics[cacheIdx] = CacheEntryStatus::PENDING;
530 // TODO: make sure that multiple parts can only be added within the same call of
531 // DataRelayer::relay
532 assert(nPayloads > 0);
533 size_t saved = 0;
534 // It's guaranteed we will see all these messages only once, so we can
535 // do the forwarding here.
536 auto allMessages = std::span<fair::mq::MessagePtr>(messages, messages + nMessages);
537 if (onInsertion) {
538 onInsertion(services, allMessages);
539 }
540 for (size_t mi = 0; mi < nMessages; ++mi) {
541 assert(mi + nPayloads < nMessages);
542 // We are in calibration mode and the data does not have the calibration bit set.
543 // We do not store it.
545 O2_SIGNPOST_ID_FROM_POINTER(cid, calibration, &services.get<DataProcessorContext>());
546 O2_SIGNPOST_EVENT_EMIT(calibration, cid, "calibration",
547 "Dropping incoming %zu messages because they are data processing.", nPayloads);
548 // Actually dropping messages.
549 for (size_t i = mi; i < mi + nPayloads + 1; i++) {
550 auto discard = std::move(messages[i]);
551 }
552 mi += nPayloads;
553 continue;
554 }
555 auto span = std::span<fair::mq::MessagePtr>(messages + mi, messages + mi + nPayloads + 1);
556 // Notice this will split [(header, payload), (header, payload)] multiparts
557 // in N different subParts for the message spec.
558 for (size_t i = 0; i < nPayloads + 1; ++i) {
559 target.emplace_back(std::move(span[i]));
560 }
561 mi += nPayloads;
562 saved += nPayloads;
563 }
564 return saved;
565 };
566
567 auto updateStatistics = [ref = mContext](TimesliceIndex::ActionTaken action) {
568 auto& stats = ref.get<DataProcessingStats>();
569
570 // Update statistics for what happened
571 switch (action) {
573 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
574 break;
576 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
577 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_COMPUTATIONS), DataProcessingStats::Op::Add, (int)1});
578 break;
580 stats.updateStats({static_cast<short>(ProcessingStatsId::RELAYED_MESSAGES), DataProcessingStats::Op::Add, (int)1});
581 break;
583 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
584 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_COMPUTATIONS), DataProcessingStats::Op::Add, (int)1});
585 break;
587 break;
588 }
589 };
590
591 // OUTER LOOP
592 //
593 // This is the actual outer loop processing input as part of a given
594 // timeslice. All the other implementation details are hidden by the lambdas
595 auto input = INVALID_INPUT;
596 auto timeslice = TimesliceId{TimesliceId::INVALID};
598 auto& index = mTimesliceIndex;
599
600 bool needsCleaning = false;
601 // First look for matching slots which already have some
602 // partial match.
603 for (size_t ci = 0; ci < index.size(); ++ci) {
604 slot = TimesliceSlot{ci};
605 if (!isSlotInLane(slot)) {
606 continue;
607 }
608 if (index.isValid(slot) == false) {
609 continue;
610 }
611 std::tie(input, timeslice) = getInputTimeslice(index.getVariablesForSlot(slot));
612 if (input != INVALID_INPUT) {
613 break;
614 }
615 }
616
617 // If we did not find anything, look for slots which
618 // are invalid.
619 if (input == INVALID_INPUT) {
620 for (size_t ci = 0; ci < index.size(); ++ci) {
621 slot = TimesliceSlot{ci};
622 if (index.isValid(slot) == true) {
623 continue;
624 }
625 if (!isSlotInLane(slot)) {
626 continue;
627 }
628 std::tie(input, timeslice) = getInputTimeslice(index.getVariablesForSlot(slot));
629 if (input != INVALID_INPUT) {
630 needsCleaning = true;
631 break;
632 }
633 }
634 }
635
636 auto& stats = mContext.get<DataProcessingStats>();
638 if (input != INVALID_INPUT && TimesliceId::isValid(timeslice) && TimesliceSlot::isValid(slot)) {
639 if (needsCleaning) {
640 this->pruneCache(slot, onDrop);
641 mPruneOps.erase(std::remove_if(mPruneOps.begin(), mPruneOps.end(), [slot](const auto& x) { return x.slot == slot; }), mPruneOps.end());
642 }
643 size_t saved = saveInSlot(timeslice, input, slot, info);
644 if (saved == 0) {
645 return RelayChoice{.type = RelayChoice::Type::Dropped, .timeslice = timeslice};
646 }
647 index.publishSlot(slot);
648 index.markAsDirty(slot, true);
649 stats.updateStats({static_cast<short>(ProcessingStatsId::RELAYED_MESSAGES), DataProcessingStats::Op::Add, (int)1});
650 return RelayChoice{.type = RelayChoice::Type::WillRelay, .timeslice = timeslice};
651 }
652
655 VariableContext pristineContext;
656 std::tie(input, timeslice) = getInputTimeslice(pristineContext);
657
658 auto DataHeaderInfo = [&rawHeader]() {
659 std::string error;
660 // extract header from message model
661 const auto* dh = o2::header::get<o2::header::DataHeader*>(rawHeader);
662 if (dh) {
663 error += fmt::format("{}/{}/{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification);
664 } else {
665 error += "invalid header";
666 }
667 return error;
668 };
669
670 if (input == INVALID_INPUT) {
671 LOG(error) << "Could not match incoming data to any input route: " << DataHeaderInfo();
672 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
673 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
674 for (size_t pi = 0; pi < nMessages; ++pi) {
675 messages[pi].reset(nullptr);
676 }
677 return RelayChoice{.type = RelayChoice::Type::Invalid, .timeslice = timeslice};
678 }
679
680 if (TimesliceId::isValid(timeslice) == false) {
681 LOG(error) << "Could not determine the timeslice for input: " << DataHeaderInfo();
682 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
683 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
684 for (size_t pi = 0; pi < nMessages; ++pi) {
685 messages[pi].reset(nullptr);
686 }
687 return RelayChoice{.type = RelayChoice::Type::Invalid, .timeslice = timeslice};
688 }
689
690 O2_SIGNPOST_ID_GENERATE(aid, data_relayer);
692 std::tie(action, slot) = index.replaceLRUWith(pristineContext, timeslice);
693 uint64_t const* debugTimestamp = std::get_if<uint64_t>(&pristineContext.get(0));
694 if (action != TimesliceIndex::ActionTaken::Wait) {
695 O2_SIGNPOST_EVENT_EMIT(data_relayer, aid, "saveInSlot",
696 "Slot %zu updated with %zu using action %d, %" PRIu64, slot.index, timeslice.value, (int)action, *debugTimestamp);
697 }
698
699 updateStatistics(action);
700
701 switch (action) {
703 return RelayChoice{.type = RelayChoice::Type::Backpressured, .timeslice = timeslice};
705 static std::atomic<size_t> obsoleteCount = 0;
706 static std::atomic<size_t> mult = 1;
707 if ((obsoleteCount++ % (1 * mult)) == 0) {
708 LOGP(warning, "Over {} incoming messages are already obsolete, not relaying.", obsoleteCount.load());
709 if (obsoleteCount > mult * 10) {
710 mult = mult * 10;
711 }
712 }
713 return RelayChoice{.type = RelayChoice::Type::Dropped, .timeslice = timeslice};
715 LOG(warning) << "Incoming data is invalid, not relaying.";
716 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
717 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
718 for (size_t pi = 0; pi < nMessages; ++pi) {
719 messages[pi].reset(nullptr);
720 }
721 return RelayChoice{.type = RelayChoice::Type::Invalid, .timeslice = timeslice};
724 // At this point the variables match the new input but the
725 // cache still holds the old data, so we prune it.
726 this->pruneCache(slot, onDrop);
727 mPruneOps.erase(std::remove_if(mPruneOps.begin(), mPruneOps.end(), [slot](const auto& x) { return x.slot == slot; }), mPruneOps.end());
728 size_t saved = saveInSlot(timeslice, input, slot, info);
729 if (saved == 0) {
730 return RelayChoice{.type = RelayChoice::Type::Dropped, .timeslice = timeslice};
731 }
732 index.publishSlot(slot);
733 index.markAsDirty(slot, true);
734 return RelayChoice{.type = RelayChoice::Type::WillRelay, .timeslice = timeslice};
735 }
737}
738
739void DataRelayer::getReadyToProcess(std::vector<DataRelayer::RecordAction>& completed)
740{
741 LOGP(debug, "DataRelayer::getReadyToProcess");
742 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
743
744 // THE STATE
745 const auto& cache = mCache;
746 const auto numInputTypes = mDistinctRoutesIndex.size();
747 //
748 // THE IMPLEMENTATION DETAILS
749 //
750 // We use this to bail out early from the check as soon as we find something
751 // which we know is not complete.
752 auto getPartialRecord = [&cache, &numInputTypes](int li) -> std::span<std::vector<fair::mq::MessagePtr> const> {
753 auto offset = li * numInputTypes;
754 assert(cache.size() >= offset + numInputTypes);
755 auto const start = cache.data() + offset;
756 auto const end = cache.data() + offset + numInputTypes;
757 return {start, end};
758 };
759
760 // These two are trivial, but in principle the whole loop could be parallelised
761 // or vectorised so "completed" could be a thread local variable which needs
762 // merging at the end.
763 auto updateCompletionResults = [&completed](TimesliceSlot li, uint64_t const* timeslice, CompletionPolicy::CompletionOp op) {
764 if (timeslice) {
765 LOGP(debug, "Doing action {} for slot {} (timeslice: {})", (int)op, li.index, *timeslice);
766 completed.emplace_back(RecordAction{li, {*timeslice}, op});
767 } else {
768 LOGP(debug, "No timeslice associated with slot ", li.index);
769 }
770 };
771
772 // THE OUTER LOOP
773 //
774 // To determine if a line is complete, we iterate on all the arguments
775 // and check if they are ready. We do it this way, because in the end
776 // the number of inputs is going to be small and having a more complex
777 // structure will probably result in a larger footprint in any case.
778 // Also notice that ai == inputsNumber only when we reach the end of the
779 // iteration, that means we have found all the required bits.
780 //
781 // Notice that the only time numInputTypes is 0 is when we are a dummy
782 // device created as a source for timers / conditions.
783 if (numInputTypes == 0) {
784 LOGP(debug, "numInputTypes == 0, returning.");
785 return;
786 }
787 size_t cacheLines = cache.size() / numInputTypes;
788 assert(cacheLines * numInputTypes == cache.size());
789 int countConsume = 0;
790 int countConsumeExisting = 0;
791 int countProcess = 0;
792 int countDiscard = 0;
793 int countWait = 0;
794 int notDirty = 0;
795
796 for (int li = cacheLines - 1; li >= 0; --li) {
797 TimesliceSlot slot{(size_t)li};
798 // We only check the cachelines which have been updated by an incoming
799 // message.
800 if (mTimesliceIndex.isDirty(slot) == false) {
801 notDirty++;
802 continue;
803 }
804 if (!mCompletionPolicy.callbackFull) {
805 throw runtime_error_f("Completion police %s has no callback set", mCompletionPolicy.name.c_str());
806 }
807 auto partial = getPartialRecord(li);
808 auto nPartsGetter = [&partial](size_t idx) {
809 return partial[idx] | count_parts{};
810 };
811 auto refCountGetter = [&partial](size_t idx) -> int {
812 auto& header = static_cast<const fair::mq::shmem::Message&>(*(partial[idx] | get_header{0}));
813 return header.GetRefCount();
814 };
815 auto indicesGetter = [&partial](size_t idx, DataRefIndices indices) -> DataRef {
816 if (!partial[idx].empty()) {
817 auto const& headerMsg = partial[idx][indices.headerIdx];
818 auto const& payloadMsg = partial[idx][indices.payloadIdx];
819 if (headerMsg) {
820 return DataRef{nullptr,
821 reinterpret_cast<const char*>(headerMsg->GetData()),
822 payloadMsg ? reinterpret_cast<char const*>(payloadMsg->GetData()) : nullptr,
823 payloadMsg ? payloadMsg->GetSize() : 0};
824 }
825 }
826 return DataRef{};
827 };
828 auto nextIndicesGetter = [&partial](size_t idx, DataRefIndices current) -> DataRefIndices {
829 auto next = partial[idx] | get_next_pair{current};
830 return next.headerIdx < partial[idx].size() ? next : DataRefIndices{size_t(-1), size_t(-1)};
831 };
832 auto payloadGetter = [&partial](size_t idx, DataRefIndices current) -> fair::mq::Message* {
833 auto const& msgs = partial[idx];
834 if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) {
835 return nullptr;
836 }
837 return msgs[current.payloadIdx].get();
838 };
839 InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, static_cast<size_t>(partial.size())};
840 CompletionPolicy::CompletionOp action = mCompletionPolicy.callbackFull(span, mInputs, mContext);
841
842 auto& variables = mTimesliceIndex.getVariablesForSlot(slot);
843 auto timeslice = std::get_if<uint64_t>(&variables.get(0));
844 switch (action) {
846 countConsume++;
847 updateCompletionResults(slot, timeslice, action);
848 mTimesliceIndex.markAsDirty(slot, false);
849 break;
851 // This is just like Consume, but we also mark all slots as dirty
852 countConsume++;
854 updateCompletionResults(slot, timeslice, action);
855 mTimesliceIndex.rescan();
856 break;
858 countConsumeExisting++;
859 updateCompletionResults(slot, timeslice, action);
860 mTimesliceIndex.markAsDirty(slot, false);
861 break;
863 countProcess++;
864 updateCompletionResults(slot, timeslice, action);
865 mTimesliceIndex.markAsDirty(slot, false);
866 break;
868 countDiscard++;
869 updateCompletionResults(slot, timeslice, action);
870 mTimesliceIndex.markAsDirty(slot, false);
871 break;
873 countWait++;
874 mTimesliceIndex.markAsDirty(slot, true);
876 break;
878 countWait++;
879 mTimesliceIndex.markAsDirty(slot, false);
880 break;
881 }
882 }
883 mTimesliceIndex.updateOldestPossibleOutput(false);
884 LOGP(debug, "DataRelayer::getReadyToProcess results notDirty:{}, consume:{}, consumeExisting:{}, process:{}, discard:{}, wait:{}",
885 notDirty, countConsume, countConsumeExisting, countProcess,
886 countDiscard, countWait);
887}
888
890{
891 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
892 const auto numInputTypes = mDistinctRoutesIndex.size();
893
894 auto markInputDone = [&cachedStateMetrics = mCachedStateMetrics,
895 &numInputTypes](TimesliceSlot s, size_t arg, CacheEntryStatus oldStatus, CacheEntryStatus newStatus) {
896 auto cacheId = s.index * numInputTypes + arg;
897 if (cachedStateMetrics[cacheId] == oldStatus) {
898 cachedStateMetrics[cacheId] = newStatus;
899 }
900 };
901
902 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
903 markInputDone(slot, ai, oldStatus, newStatus);
904 }
905}
906
907std::vector<std::vector<fair::mq::MessagePtr>> DataRelayer::consumeAllInputsForTimeslice(TimesliceSlot slot)
908{
909 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
910
911 const auto numInputTypes = mDistinctRoutesIndex.size();
912 // State of the computation
913 std::vector<std::vector<fair::mq::MessagePtr>> messages(numInputTypes);
914 auto& cache = mCache;
915 auto& index = mTimesliceIndex;
916
917 // Nothing to see here, this is just to make the outer loop more understandable.
918 auto jumpToCacheEntryAssociatedWith = [](TimesliceSlot) {
919 return;
920 };
921
922 // We move ownership so that the cache can be reused once the computation is
923 // finished. We mark the given cache slot invalid, so that it can be reused
924 // This means we can still handle old messages if there is still space in the
925 // cache where to put them.
926 auto moveHeaderPayloadToOutput = [&messages,
927 &cachedStateMetrics = mCachedStateMetrics,
928 &cache, &index, &numInputTypes](TimesliceSlot s, size_t arg) {
929 auto cacheId = s.index * numInputTypes + arg;
930 cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING;
931 // TODO: in the original implementation of the cache, there have been only two messages per entry,
932 // check if the 2 above corresponds to the number of messages.
933 if (!cache[cacheId].empty()) {
934 messages[arg] = std::move(cache[cacheId]);
935 }
936 index.markAsInvalid(s);
937 };
938
939 // An invalid set of arguments is a set of arguments associated to an invalid
940 // timeslice, so I can simply do that. I keep the assertion there because in principle
941 // we should have dispatched the timeslice already!
942 // FIXME: what happens when we have enough timeslices to hit the invalid one?
943 auto invalidateCacheFor = [&numInputTypes, &index, &cache](TimesliceSlot s) {
944 for (size_t ai = s.index * numInputTypes, ae = ai + numInputTypes; ai != ae; ++ai) {
945 assert(std::accumulate(cache[ai].begin(), cache[ai].end(), true, [](bool result, auto const& element) { return result && element.get() == nullptr; }));
946 cache[ai].clear();
947 }
948 index.markAsInvalid(s);
949 };
950
951 // Outer loop here.
952 jumpToCacheEntryAssociatedWith(slot);
953 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
954 moveHeaderPayloadToOutput(slot, ai);
955 }
956 invalidateCacheFor(slot);
957
958 return messages;
959}
960
961std::vector<std::vector<fair::mq::MessagePtr>> DataRelayer::consumeExistingInputsForTimeslice(TimesliceSlot slot)
962{
963 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
964
965 const auto numInputTypes = mDistinctRoutesIndex.size();
966 // State of the computation
967 std::vector<std::vector<fair::mq::MessagePtr>> messages(numInputTypes);
968 auto& cache = mCache;
969 auto& index = mTimesliceIndex;
970
971 // Nothing to see here, this is just to make the outer loop more understandable.
972 auto jumpToCacheEntryAssociatedWith = [](TimesliceSlot) {
973 return;
974 };
975
976 // We move ownership so that the cache can be reused once the computation is
977 // finished. We mark the given cache slot invalid, so that it can be reused
978 // This means we can still handle old messages if there is still space in the
979 // cache where to put them.
980 auto copyHeaderPayloadToOutput = [&messages,
981 &cachedStateMetrics = mCachedStateMetrics,
982 &cache, &index, &numInputTypes](TimesliceSlot s, size_t arg) {
983 auto cacheId = s.index * numInputTypes + arg;
984 cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING;
985 // TODO: in the original implementation of the cache, there have been only two messages per entry,
986 // check if the 2 above corresponds to the number of messages.
987 for (size_t pi = 0; pi < (cache[cacheId] | count_parts{}); pi++) {
988 auto& header = cache[cacheId] | get_header{pi};
989 auto&& newHeader = header->GetTransport()->CreateMessage();
990 newHeader->Copy(*header);
991 messages[arg].emplace_back(std::move(newHeader));
992 messages[arg].emplace_back(std::move(cache[cacheId] | get_payload{pi, 0}));
993 }
994 };
995
996 // Outer loop here.
997 jumpToCacheEntryAssociatedWith(slot);
998 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
999 copyHeaderPayloadToOutput(slot, ai);
1000 }
1001
1002 return std::move(messages);
1003}
1004
1006{
1007 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1008
1009 for (auto& cache : mCache) {
1010 cache.clear();
1011 }
1012 for (size_t s = 0; s < mTimesliceIndex.size(); ++s) {
1013 mTimesliceIndex.markAsInvalid(TimesliceSlot{s});
1014 }
1015}
1016
1017size_t
1019{
1020 return mCache.size() / mDistinctRoutesIndex.size();
1021}
1022
1028{
1029 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1030
1031 mTimesliceIndex.resize(s);
1032 mVariableContextes.resize(s);
1034}
1035
1037{
1038 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1039
1040 auto numInputTypes = mDistinctRoutesIndex.size();
1041 // FIXME: many of the DataRelayer function rely on allocated cache, so its
1042 // maybe misleading to have the allocation in a function primarily for
1043 // metrics publishing, do better in setPipelineLength?
1044 mCache.resize(numInputTypes * mTimesliceIndex.size());
1045 auto& states = mContext.get<DataProcessingStates>();
1046
1047 mCachedStateMetrics.resize(mCache.size());
1048
1049 // There is maximum 16 variables available. We keep them row-wise so that
1050 // that we can take mod 16 of the index to understand which variable we
1051 // are talking about.
1052 for (size_t i = 0; i < mVariableContextes.size(); ++i) {
1054 .name = fmt::format("matcher_variables/{}", i),
1055 .stateId = static_cast<short>((short)(ProcessingStateId::CONTEXT_VARIABLES_BASE) + i),
1056 .minPublishInterval = 500, // if we publish too often we flood the GUI and we are not able to read it in any case
1057 .sendInitialValue = true,
1058 .defaultEnabled = mContext.get<DriverConfig const>().driverHasGUI,
1059 });
1060 }
1061
1062 for (int ci = 0; ci < mTimesliceIndex.size(); ci++) {
1064 .name = fmt::format("data_relayer/{}", ci),
1065 .stateId = static_cast<short>((short)(ProcessingStateId::DATA_RELAYER_BASE) + (short)ci),
1066 .minPublishInterval = 800, // if we publish too often we flood the GUI and we are not able to read it in any case
1067 .sendInitialValue = true,
1068 .defaultEnabled = mContext.get<DriverConfig const>().driverHasGUI,
1069 });
1070 }
1071}
1072
1074{
1075 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1077}
1078
1080{
1081 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1083}
1084
1086{
1087 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1088 return VariableContextHelpers::getRunNumber(mTimesliceIndex.getVariablesForSlot(slot));
1089}
1090
1092{
1093 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1095}
1096
1098{
1099 if (!mContext.get<DriverConfig const>().driverHasGUI) {
1100 return;
1101 }
1102 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1103 auto& states = mContext.get<DataProcessingStates>();
1104 for (size_t ci = 0; ci < mTimesliceIndex.size(); ++ci) {
1105 auto slot = TimesliceSlot{ci};
1106 sendVariableContextMetrics(mTimesliceIndex.getPublishedVariablesForSlot(slot), slot,
1107 states);
1108 }
1109 char relayerSlotState[1024];
1110 // The number of timeslices is encoded in each state
1111 // We serialise the state of a Timeslot in a given state.
1112 int written = snprintf(relayerSlotState, 1024, "%d ", (int)mTimesliceIndex.size());
1113 char* buffer = relayerSlotState + written;
1114 for (size_t ci = 0; ci < mTimesliceIndex.size(); ++ci) {
1115 for (size_t si = 0; si < mDistinctRoutesIndex.size(); ++si) {
1116 int index = ci * mDistinctRoutesIndex.size() + si;
1117 int value = static_cast<int>(mCachedStateMetrics[index]);
1118 buffer[si] = value + '0';
1119 // Anything which is done is actually already empty,
1120 // so after we report it we mark it as such.
1121 if (mCachedStateMetrics[index] == CacheEntryStatus::DONE) {
1122 mCachedStateMetrics[index] = CacheEntryStatus::EMPTY;
1123 }
1124 }
1125 buffer[mDistinctRoutesIndex.size()] = '\0';
1126 auto size = (int)(buffer - relayerSlotState + mDistinctRoutesIndex.size());
1127 states.updateState({.id = short((int)ProcessingStateId::DATA_RELAYER_BASE + ci), .size = size, .data = relayerSlotState});
1128 }
1129}
1130
1131} // namespace o2::framework
std::vector< framework::ConcreteDataMatcher > matchers
benchmark::State & state
std::vector< OutputRoute > routes
#define O2_BUILTIN_UNREACHABLE
o2::monitoring::Verbosity Verbosity
atype::type element
std::ostringstream debug
int32_t i
uint32_t op
bool valid
#define O2_DECLARE_DYNAMIC_LOG(name)
Definition Signpost.h:490
#define O2_SIGNPOST_ID_FROM_POINTER(name, log, pointer)
Definition Signpost.h:506
#define O2_SIGNPOST_EVENT_EMIT_INFO(log, id, name, format,...)
Definition Signpost.h:532
#define O2_SIGNPOST_ID_GENERATE(name, log)
Definition Signpost.h:507
#define O2_SIGNPOST_EVENT_EMIT(log, id, name, format,...)
Definition Signpost.h:523
#define O2_LOCKABLE(T)
Definition Tracing.h:20
uint32_t getFirstTFOrbitForSlot(TimesliceSlot slot)
Get the firstTForbit associate to a given slot.
void updateCacheStatus(TimesliceSlot slot, CacheEntryStatus oldStatus, CacheEntryStatus newStatus)
uint32_t getRunNumberForSlot(TimesliceSlot slot)
Get the runNumber associated to a given slot.
void prunePending(OnDropCallback)
Prune all the pending entries in the cache.
std::function< void(TimesliceSlot, std::vector< std::span< fair::mq::MessagePtr > > &, TimesliceIndex::OldestOutputInfo info)> OnDropCallback
void getReadyToProcess(std::vector< RecordAction > &completed)
void setPipelineLength(size_t s)
Tune the maximum number of in flight timeslices this can handle.
std::vector< std::vector< fair::mq::MessagePtr > > consumeAllInputsForTimeslice(TimesliceSlot id)
size_t getParallelTimeslices() const
Returns how many timeslices we can handle in parallel.
RelayChoice relay(void const *rawHeader, std::unique_ptr< fair::mq::Message > *messages, InputInfo const &info, size_t nMessages, size_t nPayloads=1, OnInsertionCallback onInsertion=nullptr, OnDropCallback onDrop=nullptr)
void pruneCache(TimesliceSlot slot, OnDropCallback onDrop=nullptr)
Prune the cache for a given slot.
DataRelayer(CompletionPolicy const &, std::vector< InputRoute > const &routes, TimesliceIndex &, ServiceRegistryRef, int)
std::function< void(ServiceRegistryRef &, std::span< fair::mq::MessagePtr > &)> OnInsertionCallback
std::vector< std::vector< fair::mq::MessagePtr > > consumeExistingInputsForTimeslice(TimesliceSlot id)
void setOldestPossibleInput(TimesliceId timeslice, ChannelIndex channel)
uint64_t getCreationTimeForSlot(TimesliceSlot slot)
Get the creation time associated to a given slot.
void sendContextState()
Send metrics with the VariableContext information.
TimesliceId getTimesliceForSlot(TimesliceSlot slot)
ActivityStats processDanglingInputs(std::vector< ExpirationHandler > const &, ServiceRegistryRef context, bool createNew)
TimesliceIndex::OldestOutputInfo getOldestPossibleOutput() const
uint32_t getFirstTFCounterForSlot(TimesliceSlot slot)
Get the firstTFCounter associate to a given slot.
void clear()
Remove all pending messages.
virtual fair::mq::Device * device()=0
void markAsDirty(TimesliceSlot slot, bool value)
data_matcher::VariableContext & getPublishedVariablesForSlot(TimesliceSlot slot)
OldestInputInfo setOldestPossibleInput(TimesliceId timeslice, ChannelIndex channel)
OldestOutputInfo getOldestPossibleOutput() const
bool isDirty(TimesliceSlot const &slot) const
InputChannelInfo const & getChannelInfo(ChannelIndex channel) const
ActionTaken
The outcome for the processing of a given timeslot.
@ Wait
An obsolete slot is used to hold the new context and the old one is dropped.
@ DropObsolete
An invalid context is not inserted in the index and dropped.
@ DropInvalid
We wait for the oldest slot to complete.
@ ReplaceObsolete
An unused / invalid slot is used to hold the new context.
void rescan()
Mark all the cachelines as invalid, e.g. due to an out of band event.
bool validateSlot(TimesliceSlot slot, TimesliceId currentOldest)
bool isValid(TimesliceSlot const &slot) const
void markAsInvalid(TimesliceSlot slot)
OldestOutputInfo updateOldestPossibleOutput(bool rewinded)
data_matcher::VariableContext & getVariablesForSlot(TimesliceSlot slot)
void publish(void(*callback)(VariableContext const &, TimesliceSlot slot, void *context), void *context, TimesliceSlot slot)
ContextElement::Value const & get(size_t pos) const
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint buffer
Definition glcorearb.h:655
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
GLuint index
Definition glcorearb.h:781
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum target
Definition glcorearb.h:1641
GLboolean * data
Definition glcorearb.h:298
GLintptr offset
Definition glcorearb.h:660
GLsizei GLenum const void * indices
Definition glcorearb.h:400
GLuint start
Definition glcorearb.h:469
GLuint * states
Definition glcorearb.h:4932
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
size_t matchToContext(void const *data, std::vector< DataDescriptorMatcher > const &matchers, std::vector< size_t > const &index, VariableContext &context)
constexpr int INVALID_INPUT
bool isCalibrationData(std::unique_ptr< fair::mq::Message > &first)
void sendVariableContextMetrics(VariableContext &context, TimesliceSlot slot, DataProcessingStates &states)
@ NoTransition
No pending transitions.
RuntimeErrorRef runtime_error_f(const char *,...)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
void empty(int)
static constexpr int INVALID
std::string name
Name of the policy itself.
CompletionOp
Action to take with the InputRecord:
@ Retry
Like Wait but mark the cacheline as dirty.
CallbackConfigureRelayer configureRelayer
CallbackFull callbackFull
Actual policy which decides what to do with a partial InputRecord, extended version.
static constexpr int32_t KEEP_AT_EOS_FLAG
Helper struct to hold statistics about the data processing happening.
@ Add
Update the rate of the metric given the amount since the last time.
Type type
What was the outcome of the relay operation.
Definition DataRelayer.h:65
@ Invalid
Ownership of the data has been taken.
@ Backpressured
The incoming data was not valid and has been dropped.
@ Dropped
The incoming data was not relayed, because we are backpressured.
static std::string describe(InputSpec const &spec)
static unsigned int pipelineLength(unsigned int minLength)
get max number of timeslices in the queue
static bool onlineDeploymentMode()
@true if running online
Running state information of a given device.
Definition DeviceState.h:34
ProcessingType allowedProcessing
Definition DeviceState.h:79
fair::mq::Channel * channel
Definition ChannelInfo.h:51
Reference to an inflight part.
Definition PartRef.h:24
std::unique_ptr< fair::mq::Message > header
Definition PartRef.h:25
std::unique_ptr< fair::mq::Message > payload
Definition PartRef.h:26
static bool isValid(TimesliceId const &timeslice)
static constexpr uint64_t INVALID
static bool isValid(TimesliceSlot const &slot)
static constexpr uint64_t INVALID
static uint32_t getRunNumber(data_matcher::VariableContext const &variables)
static uint32_t getFirstTFCounter(data_matcher::VariableContext const &variables)
static uint64_t getCreationTime(data_matcher::VariableContext const &variables)
static uint32_t getFirstTFOrbit(data_matcher::VariableContext const &variables)
static TimesliceId getTimeslice(data_matcher::VariableContext const &variables)
the base header struct Every header type must begin (i.e. derive) with this. Don't use this struct di...
Definition DataHeader.h:351
the main header struct
Definition DataHeader.h:620
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"