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 onDrop(slot, dropped, oldestPossibleTimeslice);
438 }
439 }
440 assert(cache.empty() == false);
441 assert(index.size() * numInputTypes == cache.size());
442 // Prune old stuff from the cache, hopefully deleting it...
443 // We set the current slot to the timeslice value, so that old stuff
444 // will be ignored.
445 assert(numInputTypes * slot.index < cache.size());
446 for (size_t ai = slot.index * numInputTypes, ae = ai + numInputTypes; ai != ae; ++ai) {
447 cache[ai].clear();
448 cachedStateMetrics[ai] = CacheEntryStatus::EMPTY;
449 }
450 };
451
452 pruneCache(slot);
453}
454
455bool isCalibrationData(std::unique_ptr<fair::mq::Message>& first)
456{
457 auto* dph = o2::header::get<DataProcessingHeader*>(first->GetData());
459}
460
462 DataRelayer::relay(void const* rawHeader,
463 std::unique_ptr<fair::mq::Message>* messages,
464 InputInfo const& info,
465 size_t nMessages,
466 size_t nPayloads,
467 OnInsertionCallback onInsertion,
468 OnDropCallback onDrop)
469{
470 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
471 DataProcessingHeader const* dph = o2::header::get<DataProcessingHeader*>(rawHeader);
472 // IMPLEMENTATION DETAILS
473 //
474 // This returns true if a given slot is available for the current number of lanes
475 auto isSlotInLane = [currentLane = dph->startTime, maxLanes = mMaxLanes](TimesliceSlot slot) {
476 return (slot.index % maxLanes) == (currentLane % maxLanes);
477 };
478 // This returns the identifier for the given input. We use a separate
479 // function because while it's trivial now, the actual matchmaking will
480 // become more complicated when we will start supporting ranges.
481 auto getInputTimeslice = [&matchers = mInputMatchers,
482 &distinctRoutes = mDistinctRoutesIndex,
483 &rawHeader,
484 &index = mTimesliceIndex](VariableContext& context)
485 -> std::tuple<int, TimesliceId> {
488 auto input = matchToContext(rawHeader, matchers, distinctRoutes, context);
489
490 if (input == INVALID_INPUT) {
491 return {
494 };
495 }
498 if (auto pval = std::get_if<uint64_t>(&context.get(0))) {
499 TimesliceId timeslice{*pval};
500 return {input, timeslice};
501 }
502 // If we get here it means we need to push something out of the cache.
503 return {
506 };
507 };
508
509 // Actually save the header / payload in the slot
510 auto saveInSlot = [&cachedStateMetrics = mCachedStateMetrics,
511 &messages,
512 &nMessages,
513 &nPayloads,
514 &onInsertion,
515 &cache = mCache,
516 &services = mContext,
517 numInputTypes = mDistinctRoutesIndex.size()](TimesliceId timeslice, int input, TimesliceSlot slot, InputInfo const& info) -> size_t {
518 O2_SIGNPOST_ID_GENERATE(aid, data_relayer);
519 O2_SIGNPOST_EVENT_EMIT(data_relayer, aid, "saveInSlot", "saving %{public}s@%zu in slot %zu from %{public}s",
520 fmt::format("{:x}", *o2::header::get<DataHeader*>(messages[0]->GetData())).c_str(),
521 timeslice.value, slot.index,
522 info.index.value == ChannelIndex::INVALID ? "invalid" : services.get<FairMQDeviceProxy>().getInputChannel(info.index)->GetName().c_str());
523 auto cacheIdx = numInputTypes * slot.index + input;
524 auto& target = cache[cacheIdx];
525 cachedStateMetrics[cacheIdx] = CacheEntryStatus::PENDING;
526 // TODO: make sure that multiple parts can only be added within the same call of
527 // DataRelayer::relay
528 assert(nPayloads > 0);
529 size_t saved = 0;
530 // It's guaranteed we will see all these messages only once, so we can
531 // do the forwarding here.
532 auto allMessages = std::span<fair::mq::MessagePtr>(messages, messages + nMessages);
533 if (onInsertion) {
534 onInsertion(services, allMessages);
535 }
536 for (size_t mi = 0; mi < nMessages; ++mi) {
537 assert(mi + nPayloads < nMessages);
538 // We are in calibration mode and the data does not have the calibration bit set.
539 // We do not store it.
541 O2_SIGNPOST_ID_FROM_POINTER(cid, calibration, &services.get<DataProcessorContext>());
542 O2_SIGNPOST_EVENT_EMIT(calibration, cid, "calibration",
543 "Dropping incoming %zu messages because they are data processing.", nPayloads);
544 // Actually dropping messages.
545 for (size_t i = mi; i < mi + nPayloads + 1; i++) {
546 auto discard = std::move(messages[i]);
547 }
548 mi += nPayloads;
549 continue;
550 }
551 auto span = std::span<fair::mq::MessagePtr>(messages + mi, messages + mi + nPayloads + 1);
552 // Notice this will split [(header, payload), (header, payload)] multiparts
553 // in N different subParts for the message spec.
554 for (size_t i = 0; i < nPayloads + 1; ++i) {
555 target.emplace_back(std::move(span[i]));
556 }
557 mi += nPayloads;
558 saved += nPayloads;
559 }
560 return saved;
561 };
562
563 auto updateStatistics = [ref = mContext](TimesliceIndex::ActionTaken action) {
564 auto& stats = ref.get<DataProcessingStats>();
565
566 // Update statistics for what happened
567 switch (action) {
569 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
570 break;
572 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
573 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_COMPUTATIONS), DataProcessingStats::Op::Add, (int)1});
574 break;
576 stats.updateStats({static_cast<short>(ProcessingStatsId::RELAYED_MESSAGES), DataProcessingStats::Op::Add, (int)1});
577 break;
579 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
580 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_COMPUTATIONS), DataProcessingStats::Op::Add, (int)1});
581 break;
583 break;
584 }
585 };
586
587 // OUTER LOOP
588 //
589 // This is the actual outer loop processing input as part of a given
590 // timeslice. All the other implementation details are hidden by the lambdas
591 auto input = INVALID_INPUT;
592 auto timeslice = TimesliceId{TimesliceId::INVALID};
594 auto& index = mTimesliceIndex;
595
596 bool needsCleaning = false;
597 // First look for matching slots which already have some
598 // partial match.
599 for (size_t ci = 0; ci < index.size(); ++ci) {
600 slot = TimesliceSlot{ci};
601 if (!isSlotInLane(slot)) {
602 continue;
603 }
604 if (index.isValid(slot) == false) {
605 continue;
606 }
607 std::tie(input, timeslice) = getInputTimeslice(index.getVariablesForSlot(slot));
608 if (input != INVALID_INPUT) {
609 break;
610 }
611 }
612
613 // If we did not find anything, look for slots which
614 // are invalid.
615 if (input == INVALID_INPUT) {
616 for (size_t ci = 0; ci < index.size(); ++ci) {
617 slot = TimesliceSlot{ci};
618 if (index.isValid(slot) == true) {
619 continue;
620 }
621 if (!isSlotInLane(slot)) {
622 continue;
623 }
624 std::tie(input, timeslice) = getInputTimeslice(index.getVariablesForSlot(slot));
625 if (input != INVALID_INPUT) {
626 needsCleaning = true;
627 break;
628 }
629 }
630 }
631
632 auto& stats = mContext.get<DataProcessingStats>();
634 if (input != INVALID_INPUT && TimesliceId::isValid(timeslice) && TimesliceSlot::isValid(slot)) {
635 if (needsCleaning) {
636 this->pruneCache(slot, onDrop);
637 mPruneOps.erase(std::remove_if(mPruneOps.begin(), mPruneOps.end(), [slot](const auto& x) { return x.slot == slot; }), mPruneOps.end());
638 }
639 size_t saved = saveInSlot(timeslice, input, slot, info);
640 if (saved == 0) {
641 return RelayChoice{.type = RelayChoice::Type::Dropped, .timeslice = timeslice};
642 }
643 index.publishSlot(slot);
644 index.markAsDirty(slot, true);
645 stats.updateStats({static_cast<short>(ProcessingStatsId::RELAYED_MESSAGES), DataProcessingStats::Op::Add, (int)1});
646 return RelayChoice{.type = RelayChoice::Type::WillRelay, .timeslice = timeslice};
647 }
648
651 VariableContext pristineContext;
652 std::tie(input, timeslice) = getInputTimeslice(pristineContext);
653
654 auto DataHeaderInfo = [&rawHeader]() {
655 std::string error;
656 // extract header from message model
657 const auto* dh = o2::header::get<o2::header::DataHeader*>(rawHeader);
658 if (dh) {
659 error += fmt::format("{}/{}/{}", dh->dataOrigin, dh->dataDescription, dh->subSpecification);
660 } else {
661 error += "invalid header";
662 }
663 return error;
664 };
665
666 if (input == INVALID_INPUT) {
667 LOG(error) << "Could not match incoming data to any input route: " << DataHeaderInfo();
668 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
669 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
670 for (size_t pi = 0; pi < nMessages; ++pi) {
671 messages[pi].reset(nullptr);
672 }
673 return RelayChoice{.type = RelayChoice::Type::Invalid, .timeslice = timeslice};
674 }
675
676 if (TimesliceId::isValid(timeslice) == false) {
677 LOG(error) << "Could not determine the timeslice for input: " << DataHeaderInfo();
678 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
679 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
680 for (size_t pi = 0; pi < nMessages; ++pi) {
681 messages[pi].reset(nullptr);
682 }
683 return RelayChoice{.type = RelayChoice::Type::Invalid, .timeslice = timeslice};
684 }
685
686 O2_SIGNPOST_ID_GENERATE(aid, data_relayer);
688 std::tie(action, slot) = index.replaceLRUWith(pristineContext, timeslice);
689 uint64_t const* debugTimestamp = std::get_if<uint64_t>(&pristineContext.get(0));
690 if (action != TimesliceIndex::ActionTaken::Wait) {
691 O2_SIGNPOST_EVENT_EMIT(data_relayer, aid, "saveInSlot",
692 "Slot %zu updated with %zu using action %d, %" PRIu64, slot.index, timeslice.value, (int)action, *debugTimestamp);
693 }
694
695 updateStatistics(action);
696
697 switch (action) {
699 return RelayChoice{.type = RelayChoice::Type::Backpressured, .timeslice = timeslice};
701 static std::atomic<size_t> obsoleteCount = 0;
702 static std::atomic<size_t> mult = 1;
703 if ((obsoleteCount++ % (1 * mult)) == 0) {
704 LOGP(warning, "Over {} incoming messages are already obsolete, not relaying.", obsoleteCount.load());
705 if (obsoleteCount > mult * 10) {
706 mult = mult * 10;
707 }
708 }
709 return RelayChoice{.type = RelayChoice::Type::Dropped, .timeslice = timeslice};
711 LOG(warning) << "Incoming data is invalid, not relaying.";
712 stats.updateStats({static_cast<short>(ProcessingStatsId::MALFORMED_INPUTS), DataProcessingStats::Op::Add, (int)1});
713 stats.updateStats({static_cast<short>(ProcessingStatsId::DROPPED_INCOMING_MESSAGES), DataProcessingStats::Op::Add, (int)1});
714 for (size_t pi = 0; pi < nMessages; ++pi) {
715 messages[pi].reset(nullptr);
716 }
717 return RelayChoice{.type = RelayChoice::Type::Invalid, .timeslice = timeslice};
720 // At this point the variables match the new input but the
721 // cache still holds the old data, so we prune it.
722 this->pruneCache(slot, onDrop);
723 mPruneOps.erase(std::remove_if(mPruneOps.begin(), mPruneOps.end(), [slot](const auto& x) { return x.slot == slot; }), mPruneOps.end());
724 size_t saved = saveInSlot(timeslice, input, slot, info);
725 if (saved == 0) {
726 return RelayChoice{.type = RelayChoice::Type::Dropped, .timeslice = timeslice};
727 }
728 index.publishSlot(slot);
729 index.markAsDirty(slot, true);
730 return RelayChoice{.type = RelayChoice::Type::WillRelay, .timeslice = timeslice};
731 }
733}
734
735void DataRelayer::getReadyToProcess(std::vector<DataRelayer::RecordAction>& completed)
736{
737 LOGP(debug, "DataRelayer::getReadyToProcess");
738 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
739
740 // THE STATE
741 const auto& cache = mCache;
742 const auto numInputTypes = mDistinctRoutesIndex.size();
743 //
744 // THE IMPLEMENTATION DETAILS
745 //
746 // We use this to bail out early from the check as soon as we find something
747 // which we know is not complete.
748 auto getPartialRecord = [&cache, &numInputTypes](int li) -> std::span<std::vector<fair::mq::MessagePtr> const> {
749 auto offset = li * numInputTypes;
750 assert(cache.size() >= offset + numInputTypes);
751 auto const start = cache.data() + offset;
752 auto const end = cache.data() + offset + numInputTypes;
753 return {start, end};
754 };
755
756 // These two are trivial, but in principle the whole loop could be parallelised
757 // or vectorised so "completed" could be a thread local variable which needs
758 // merging at the end.
759 auto updateCompletionResults = [&completed](TimesliceSlot li, uint64_t const* timeslice, CompletionPolicy::CompletionOp op) {
760 if (timeslice) {
761 LOGP(debug, "Doing action {} for slot {} (timeslice: {})", (int)op, li.index, *timeslice);
762 completed.emplace_back(RecordAction{li, {*timeslice}, op});
763 } else {
764 LOGP(debug, "No timeslice associated with slot ", li.index);
765 }
766 };
767
768 // THE OUTER LOOP
769 //
770 // To determine if a line is complete, we iterate on all the arguments
771 // and check if they are ready. We do it this way, because in the end
772 // the number of inputs is going to be small and having a more complex
773 // structure will probably result in a larger footprint in any case.
774 // Also notice that ai == inputsNumber only when we reach the end of the
775 // iteration, that means we have found all the required bits.
776 //
777 // Notice that the only time numInputTypes is 0 is when we are a dummy
778 // device created as a source for timers / conditions.
779 if (numInputTypes == 0) {
780 LOGP(debug, "numInputTypes == 0, returning.");
781 return;
782 }
783 size_t cacheLines = cache.size() / numInputTypes;
784 assert(cacheLines * numInputTypes == cache.size());
785 int countConsume = 0;
786 int countConsumeExisting = 0;
787 int countProcess = 0;
788 int countDiscard = 0;
789 int countWait = 0;
790 int notDirty = 0;
791
792 for (int li = cacheLines - 1; li >= 0; --li) {
793 TimesliceSlot slot{(size_t)li};
794 // We only check the cachelines which have been updated by an incoming
795 // message.
796 if (mTimesliceIndex.isDirty(slot) == false) {
797 notDirty++;
798 continue;
799 }
800 if (!mCompletionPolicy.callbackFull) {
801 throw runtime_error_f("Completion police %s has no callback set", mCompletionPolicy.name.c_str());
802 }
803 auto partial = getPartialRecord(li);
804 auto nPartsGetter = [&partial](size_t idx) {
805 return partial[idx] | count_parts{};
806 };
807 auto refCountGetter = [&partial](size_t idx) -> int {
808 auto& header = static_cast<const fair::mq::shmem::Message&>(*(partial[idx] | get_header{0}));
809 return header.GetRefCount();
810 };
811 auto indicesGetter = [&partial](size_t idx, DataRefIndices indices) -> DataRef {
812 if (!partial[idx].empty()) {
813 auto const& headerMsg = partial[idx][indices.headerIdx];
814 auto const& payloadMsg = partial[idx][indices.payloadIdx];
815 if (headerMsg) {
816 return DataRef{nullptr,
817 reinterpret_cast<const char*>(headerMsg->GetData()),
818 payloadMsg ? reinterpret_cast<char const*>(payloadMsg->GetData()) : nullptr,
819 payloadMsg ? payloadMsg->GetSize() : 0};
820 }
821 }
822 return DataRef{};
823 };
824 auto nextIndicesGetter = [&partial](size_t idx, DataRefIndices current) -> DataRefIndices {
825 auto next = partial[idx] | get_next_pair{current};
826 return next.headerIdx < partial[idx].size() ? next : DataRefIndices{size_t(-1), size_t(-1)};
827 };
828 auto payloadGetter = [&partial](size_t idx, DataRefIndices current) -> fair::mq::Message* {
829 auto const& msgs = partial[idx];
830 if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) {
831 return nullptr;
832 }
833 return msgs[current.payloadIdx].get();
834 };
835 InputSpan span{nPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, static_cast<size_t>(partial.size())};
836 CompletionPolicy::CompletionOp action = mCompletionPolicy.callbackFull(span, mInputs, mContext);
837
838 auto& variables = mTimesliceIndex.getVariablesForSlot(slot);
839 auto timeslice = std::get_if<uint64_t>(&variables.get(0));
840 switch (action) {
842 countConsume++;
843 updateCompletionResults(slot, timeslice, action);
844 mTimesliceIndex.markAsDirty(slot, false);
845 break;
847 // This is just like Consume, but we also mark all slots as dirty
848 countConsume++;
850 updateCompletionResults(slot, timeslice, action);
851 mTimesliceIndex.rescan();
852 break;
854 countConsumeExisting++;
855 updateCompletionResults(slot, timeslice, action);
856 mTimesliceIndex.markAsDirty(slot, false);
857 break;
859 countProcess++;
860 updateCompletionResults(slot, timeslice, action);
861 mTimesliceIndex.markAsDirty(slot, false);
862 break;
864 countDiscard++;
865 updateCompletionResults(slot, timeslice, action);
866 mTimesliceIndex.markAsDirty(slot, false);
867 break;
869 countWait++;
870 mTimesliceIndex.markAsDirty(slot, true);
872 break;
874 countWait++;
875 mTimesliceIndex.markAsDirty(slot, false);
876 break;
877 }
878 }
879 mTimesliceIndex.updateOldestPossibleOutput(false);
880 LOGP(debug, "DataRelayer::getReadyToProcess results notDirty:{}, consume:{}, consumeExisting:{}, process:{}, discard:{}, wait:{}",
881 notDirty, countConsume, countConsumeExisting, countProcess,
882 countDiscard, countWait);
883}
884
886{
887 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
888 const auto numInputTypes = mDistinctRoutesIndex.size();
889
890 auto markInputDone = [&cachedStateMetrics = mCachedStateMetrics,
891 &numInputTypes](TimesliceSlot s, size_t arg, CacheEntryStatus oldStatus, CacheEntryStatus newStatus) {
892 auto cacheId = s.index * numInputTypes + arg;
893 if (cachedStateMetrics[cacheId] == oldStatus) {
894 cachedStateMetrics[cacheId] = newStatus;
895 }
896 };
897
898 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
899 markInputDone(slot, ai, oldStatus, newStatus);
900 }
901}
902
903std::vector<std::vector<fair::mq::MessagePtr>> DataRelayer::consumeAllInputsForTimeslice(TimesliceSlot slot)
904{
905 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
906
907 const auto numInputTypes = mDistinctRoutesIndex.size();
908 // State of the computation
909 std::vector<std::vector<fair::mq::MessagePtr>> messages(numInputTypes);
910 auto& cache = mCache;
911 auto& index = mTimesliceIndex;
912
913 // Nothing to see here, this is just to make the outer loop more understandable.
914 auto jumpToCacheEntryAssociatedWith = [](TimesliceSlot) {
915 return;
916 };
917
918 // We move ownership so that the cache can be reused once the computation is
919 // finished. We mark the given cache slot invalid, so that it can be reused
920 // This means we can still handle old messages if there is still space in the
921 // cache where to put them.
922 auto moveHeaderPayloadToOutput = [&messages,
923 &cachedStateMetrics = mCachedStateMetrics,
924 &cache, &index, &numInputTypes](TimesliceSlot s, size_t arg) {
925 auto cacheId = s.index * numInputTypes + arg;
926 cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING;
927 // TODO: in the original implementation of the cache, there have been only two messages per entry,
928 // check if the 2 above corresponds to the number of messages.
929 if (!cache[cacheId].empty()) {
930 messages[arg] = std::move(cache[cacheId]);
931 }
932 index.markAsInvalid(s);
933 };
934
935 // An invalid set of arguments is a set of arguments associated to an invalid
936 // timeslice, so I can simply do that. I keep the assertion there because in principle
937 // we should have dispatched the timeslice already!
938 // FIXME: what happens when we have enough timeslices to hit the invalid one?
939 auto invalidateCacheFor = [&numInputTypes, &index, &cache](TimesliceSlot s) {
940 for (size_t ai = s.index * numInputTypes, ae = ai + numInputTypes; ai != ae; ++ai) {
941 assert(std::accumulate(cache[ai].begin(), cache[ai].end(), true, [](bool result, auto const& element) { return result && element.get() == nullptr; }));
942 cache[ai].clear();
943 }
944 index.markAsInvalid(s);
945 };
946
947 // Outer loop here.
948 jumpToCacheEntryAssociatedWith(slot);
949 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
950 moveHeaderPayloadToOutput(slot, ai);
951 }
952 invalidateCacheFor(slot);
953
954 return messages;
955}
956
957std::vector<std::vector<fair::mq::MessagePtr>> DataRelayer::consumeExistingInputsForTimeslice(TimesliceSlot slot)
958{
959 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
960
961 const auto numInputTypes = mDistinctRoutesIndex.size();
962 // State of the computation
963 std::vector<std::vector<fair::mq::MessagePtr>> messages(numInputTypes);
964 auto& cache = mCache;
965 auto& index = mTimesliceIndex;
966
967 // Nothing to see here, this is just to make the outer loop more understandable.
968 auto jumpToCacheEntryAssociatedWith = [](TimesliceSlot) {
969 return;
970 };
971
972 // We move ownership so that the cache can be reused once the computation is
973 // finished. We mark the given cache slot invalid, so that it can be reused
974 // This means we can still handle old messages if there is still space in the
975 // cache where to put them.
976 auto copyHeaderPayloadToOutput = [&messages,
977 &cachedStateMetrics = mCachedStateMetrics,
978 &cache, &index, &numInputTypes](TimesliceSlot s, size_t arg) {
979 auto cacheId = s.index * numInputTypes + arg;
980 cachedStateMetrics[cacheId] = CacheEntryStatus::RUNNING;
981 // TODO: in the original implementation of the cache, there have been only two messages per entry,
982 // check if the 2 above corresponds to the number of messages.
983 for (size_t pi = 0; pi < (cache[cacheId] | count_parts{}); pi++) {
984 auto& header = cache[cacheId] | get_header{pi};
985 auto&& newHeader = header->GetTransport()->CreateMessage();
986 newHeader->Copy(*header);
987 messages[arg].emplace_back(std::move(newHeader));
988 messages[arg].emplace_back(std::move(cache[cacheId] | get_payload{pi, 0}));
989 }
990 };
991
992 // Outer loop here.
993 jumpToCacheEntryAssociatedWith(slot);
994 for (size_t ai = 0, ae = numInputTypes; ai != ae; ++ai) {
995 copyHeaderPayloadToOutput(slot, ai);
996 }
997
998 return std::move(messages);
999}
1000
1002{
1003 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1004
1005 for (auto& cache : mCache) {
1006 cache.clear();
1007 }
1008 for (size_t s = 0; s < mTimesliceIndex.size(); ++s) {
1009 mTimesliceIndex.markAsInvalid(TimesliceSlot{s});
1010 }
1011}
1012
1013size_t
1015{
1016 return mCache.size() / mDistinctRoutesIndex.size();
1017}
1018
1024{
1025 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1026
1027 mTimesliceIndex.resize(s);
1028 mVariableContextes.resize(s);
1030}
1031
1033{
1034 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1035
1036 auto numInputTypes = mDistinctRoutesIndex.size();
1037 // FIXME: many of the DataRelayer function rely on allocated cache, so its
1038 // maybe misleading to have the allocation in a function primarily for
1039 // metrics publishing, do better in setPipelineLength?
1040 mCache.resize(numInputTypes * mTimesliceIndex.size());
1041 auto& states = mContext.get<DataProcessingStates>();
1042
1043 mCachedStateMetrics.resize(mCache.size());
1044
1045 // There is maximum 16 variables available. We keep them row-wise so that
1046 // that we can take mod 16 of the index to understand which variable we
1047 // are talking about.
1048 for (size_t i = 0; i < mVariableContextes.size(); ++i) {
1050 .name = fmt::format("matcher_variables/{}", i),
1051 .stateId = static_cast<short>((short)(ProcessingStateId::CONTEXT_VARIABLES_BASE) + i),
1052 .minPublishInterval = 500, // if we publish too often we flood the GUI and we are not able to read it in any case
1053 .sendInitialValue = true,
1054 .defaultEnabled = mContext.get<DriverConfig const>().driverHasGUI,
1055 });
1056 }
1057
1058 for (int ci = 0; ci < mTimesliceIndex.size(); ci++) {
1060 .name = fmt::format("data_relayer/{}", ci),
1061 .stateId = static_cast<short>((short)(ProcessingStateId::DATA_RELAYER_BASE) + (short)ci),
1062 .minPublishInterval = 800, // if we publish too often we flood the GUI and we are not able to read it in any case
1063 .sendInitialValue = true,
1064 .defaultEnabled = mContext.get<DriverConfig const>().driverHasGUI,
1065 });
1066 }
1067}
1068
1070{
1071 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1073}
1074
1076{
1077 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1079}
1080
1082{
1083 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1084 return VariableContextHelpers::getRunNumber(mTimesliceIndex.getVariablesForSlot(slot));
1085}
1086
1088{
1089 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1091}
1092
1094{
1095 if (!mContext.get<DriverConfig const>().driverHasGUI) {
1096 return;
1097 }
1098 std::scoped_lock<O2_LOCKABLE(std::recursive_mutex)> lock(mMutex);
1099 auto& states = mContext.get<DataProcessingStates>();
1100 for (size_t ci = 0; ci < mTimesliceIndex.size(); ++ci) {
1101 auto slot = TimesliceSlot{ci};
1102 sendVariableContextMetrics(mTimesliceIndex.getPublishedVariablesForSlot(slot), slot,
1103 states);
1104 }
1105 char relayerSlotState[1024];
1106 // The number of timeslices is encoded in each state
1107 // We serialise the state of a Timeslot in a given state.
1108 int written = snprintf(relayerSlotState, 1024, "%d ", (int)mTimesliceIndex.size());
1109 char* buffer = relayerSlotState + written;
1110 for (size_t ci = 0; ci < mTimesliceIndex.size(); ++ci) {
1111 for (size_t si = 0; si < mDistinctRoutesIndex.size(); ++si) {
1112 int index = ci * mDistinctRoutesIndex.size() + si;
1113 int value = static_cast<int>(mCachedStateMetrics[index]);
1114 buffer[si] = value + '0';
1115 // Anything which is done is actually already empty,
1116 // so after we report it we mark it as such.
1117 if (mCachedStateMetrics[index] == CacheEntryStatus::DONE) {
1118 mCachedStateMetrics[index] = CacheEntryStatus::EMPTY;
1119 }
1120 }
1121 buffer[mDistinctRoutesIndex.size()] = '\0';
1122 auto size = (int)(buffer - relayerSlotState + mDistinctRoutesIndex.size());
1123 states.updateState({.id = short((int)ProcessingStateId::DATA_RELAYER_BASE + ci), .size = size, .data = relayerSlotState});
1124 }
1125}
1126
1127} // 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.
void getReadyToProcess(std::vector< RecordAction > &completed)
std::function< void(TimesliceSlot, std::vector< std::vector< fair::mq::MessagePtr > > &, TimesliceIndex::OldestOutputInfo info)> OnDropCallback
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:64
@ 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"