159 int rateLimitingIPCID = std::stoi(ctx.options().get<std::string>(
"timeframes-rate-limit-ipcid"));
161 .
name =
"internal-dpl-ccdb-backend",
164 {
"condition-timestamp",
VariantType::Int64, 0ll, {
"Force timestamp for CCDB lookup"}},
165 {
"condition-not-before",
VariantType::Int64, 0ll, {
"do not fetch from CCDB objects created before provide timestamp"}},
166 {
"condition-not-after",
VariantType::Int64, 3385078236000ll, {
"do not fetch from CCDB objects created after the timestamp"}},
167 {
"condition-remap",
VariantType::String,
"", {
"remap condition path in CCDB based on the provided string."}},
170 {
"condition-use-slice-for-prescaling",
VariantType::Int, 0, {
"use TFslice instead of TFcounter to control validation frequency. If > query rate, do not allow TFCounter excursion exceeding it"}},
171 {
"condition-time-tolerance",
VariantType::Int64, 5000ll, {
"prefer creation time if its difference to orbit-derived time exceeds threshold (ms), impose if <0"}},
172 {
"orbit-offset-enumeration",
VariantType::Int64, 0ll, {
"initial value for the orbit"}},
173 {
"orbit-multiplier-enumeration",
VariantType::Int64, 0ll, {
"multiplier to get the orbit from the counter"}},
174 {
"start-value-enumeration",
VariantType::Int64, 0ll, {
"initial value for the enumeration"}},
175 {
"end-value-enumeration",
VariantType::Int64, -1ll, {
"final value for the enumeration"}},
176 {
"step-value-enumeration",
VariantType::Int64, 1ll, {
"step between one value and the other"}}},
179 .
name =
"internal-dpl-aod-ccdb",
184 {
"condition-not-before",
VariantType::Int64, 0ll, {
"do not fetch from CCDB objects created before provide timestamp"}},
185 {
"condition-not-after",
VariantType::Int64, 3385078236000ll, {
"do not fetch from CCDB objects created after the timestamp"}},
186 {
"condition-remap",
VariantType::String,
"", {
"remap condition path in CCDB based on the provided string."}},
189 {
"condition-use-slice-for-prescaling",
VariantType::Int, 0, {
"use TFslice instead of TFcounter to control validation frequency. If > query rate, do not allow TFCounter excursion exceeding it"}},
190 {
"condition-time-tolerance",
VariantType::Int64, 5000ll, {
"prefer creation time if its difference to orbit-derived time exceeds threshold (ms), impose if <0"}},
191 {
"start-value-enumeration",
VariantType::Int64, 0ll, {
"initial value for the enumeration"}},
192 {
"end-value-enumeration",
VariantType::Int64, -1ll, {
"final value for the enumeration"}},
193 {
"step-value-enumeration",
VariantType::Int64, 1ll, {
"step between one value and the other"}}}};
214 auto aodLifetime = Lifetime::Enumeration;
217 .
name =
"internal-dpl-aod-reader",
235 ctx.services().registerService(ServiceRegistryHelpers::handleForService<DanglingEdgesContext>(
new DanglingEdgesContext));
238 std::vector<InputSpec> requestedCCDBs;
239 std::vector<OutputSpec> providedCCDBs;
241 for (
size_t wi = 0; wi < workflow.size(); ++wi) {
242 auto& processor = workflow[wi];
243 auto name = processor.name;
245 dec.outTskMap.push_back({
hash,
name});
247 std::string prefix =
"internal-dpl-";
248 if (processor.inputs.empty() && processor.name.compare(0, prefix.size(), prefix) != 0) {
256 bool hasTimeframeInputs = std::ranges::any_of(processor.inputs, [](
auto const& input) { return input.lifetime == Lifetime::Timeframe; });
257 bool hasTimeframeOutputs = std::ranges::any_of(processor.outputs, [](
auto const&
output) { return output.lifetime == Lifetime::Timeframe; });
261 bool timeframeSink = hasTimeframeInputs && !hasTimeframeOutputs;
262 if (rateLimitingIPCID != -1) {
263 if (timeframeSink && processor.name.find(
"internal-dpl-injected-dummy-sink") == std::string::npos) {
265 bool hasMatch =
false;
267 auto summaryOutput = std::ranges::find_if(processor.outputs, [&summaryMatcher](
auto const&
output) { return DataSpecUtils::match(output, summaryMatcher); });
268 if (summaryOutput != processor.outputs.end()) {
269 O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid,
"output enumeration",
"%{public}s already there in %{public}s",
275 O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid,
"output enumeration",
"Adding DPL/SUMMARY/%d to %{public}s",
hash, processor.name.c_str());
280 bool hasConditionOption =
false;
281 for (
size_t ii = 0; ii < processor.inputs.size(); ++ii) {
282 auto& input = processor.inputs[ii];
283 bool hasProjectors =
false;
284 bool hasIndexRecords =
false;
285 bool hasCCDBURLs =
false;
288 for (
auto const& p : input.metadata) {
289 if (
p.name.starts_with(
"aod-origin-replaced")) {
292 if (
p.name.compare(
"projectors") == 0) {
293 hasProjectors =
true;
296 if (
p.name.compare(
"index-records") == 0) {
297 hasIndexRecords =
true;
300 if (
p.name.starts_with(
"ccdb:")) {
305 switch (input.lifetime) {
306 case Lifetime::Timer: {
308 auto hasOption = std::ranges::any_of(processor.options, [&input](
auto const& option) { return (option.name ==
"period-" + input.binding); });
309 if (hasOption ==
false) {
310 processor.options.push_back(ConfigParamSpec{
"period-" + input.binding,
VariantType::Int, 1000, {
"period of the timer in milliseconds"}});
312 timer.outputs.emplace_back(
OutputSpec{concrete.origin, concrete.description, concrete.subSpec, Lifetime::Timer});
314 case Lifetime::Signal: {
316 timer.outputs.emplace_back(
OutputSpec{concrete.origin, concrete.description, concrete.subSpec, Lifetime::Signal});
318 case Lifetime::Enumeration: {
320 timer.outputs.emplace_back(
OutputSpec{concrete.origin, concrete.description, concrete.subSpec, Lifetime::Enumeration});
322 case Lifetime::Condition: {
323 requestedCCDBs.emplace_back(input);
324 if ((hasConditionOption ==
false) && std::ranges::none_of(processor.options, [](
auto const& option) { return (option.name.compare(
"condition-backend") == 0); })) {
326 processor.options.emplace_back(ConfigParamSpec{
"condition-timestamp",
VariantType::Int64, 0ll, {
"Force timestamp for CCDB lookup"}});
327 hasConditionOption =
true;
330 case Lifetime::OutOfBand: {
332 auto hasOption = std::ranges::any_of(processor.options, [&input](
auto const& option) { return (option.name ==
"out-of-band-channel-name-" + input.binding); });
333 if (hasOption ==
false) {
334 processor.options.push_back(ConfigParamSpec{
"out-of-band-channel-name-" + input.binding,
VariantType::String,
"out-of-band", {
"channel to listen for out of band data"}});
336 timer.outputs.emplace_back(
OutputSpec{concrete.origin, concrete.description, concrete.subSpec, Lifetime::Enumeration});
339 case Lifetime::Transient:
340 case Lifetime::Timeframe:
341 case Lifetime::Optional:
346 }
else if (hasIndexRecords) {
348 }
else if (hasCCDBURLs) {
355 std::ranges::stable_sort(timer.outputs, [](
OutputSpec const&
a,
OutputSpec const&
b) { return *DataSpecUtils::getOptionalSubSpec(a) < *DataSpecUtils::getOptionalSubSpec(b); });
357 for (
auto&
output : processor.outputs) {
358 bool hasProjectors =
false;
359 bool hasIndexRecords =
false;
360 bool hasCCDBURLs =
false;
363 for (
auto const& p :
output.metadata) {
364 if (
p.name.starts_with(
"aod-origin-replaced")) {
367 if (
p.name.compare(
"projectors") == 0) {
368 hasProjectors =
true;
371 if (
p.name.compare(
"index-records") == 0) {
372 hasIndexRecords =
true;
375 if (
p.name.starts_with(
"ccdb:")) {
381 dec.providedDYNs.emplace_back(
output);
382 }
else if (hasCCDBURLs) {
383 dec.providedTIMs.emplace_back(
output);
384 }
else if (hasIndexRecords) {
385 dec.providedIDXs.emplace_back(
output);
387 dec.providedAODs.emplace_back(
output);
389 dec.providedOutputObjHist.emplace_back(
output);
390 auto it = std::ranges::find_if(dec.outObjHistMap, [&](
auto&&
x) { return x.id == hash; });
391 if (it == dec.outObjHistMap.end()) {
392 dec.outObjHistMap.push_back({
hash, {
output.binding.value}});
394 it->bindings.push_back(
output.binding.value);
398 if (
output.lifetime == Lifetime::Condition) {
399 providedCCDBs.push_back(
output);
408 "internal-dpl-aod-index-builder",
413 std::ranges::sort(dec.requestedIDXs, inputSpecLessThan);
414 std::ranges::sort(dec.providedIDXs, outputSpecLessThan);
415 dec.requestedIDXs | views::filter_not_matching(dec.providedIDXs) | sinks::append_to{dec.builderInputs};
418 std::ranges::sort(dec.requestedTIMs, inputSpecLessThan);
419 std::ranges::sort(dec.providedTIMs, outputSpecLessThan);
420 dec.requestedTIMs | views::filter_not_matching(dec.providedTIMs) | sinks::append_to{dec.analysisCCDBInputs};
423 std::ranges::sort(dec.requestedDYNs, inputSpecLessThan);
424 std::ranges::sort(dec.providedDYNs, outputSpecLessThan);
425 dec.requestedDYNs | views::filter_not_matching(dec.providedDYNs) | sinks::append_to{dec.spawnerInputs};
428 "internal-dpl-aod-spawner",
435 std::ranges::sort(dec.requestedAODs, inputSpecLessThan);
436 std::ranges::sort(dec.providedAODs, outputSpecLessThan);
439 std::ranges::sort(requestedCCDBs, inputSpecLessThan);
440 std::ranges::sort(providedCCDBs, outputSpecLessThan);
443 std::vector<DataProcessorSpec> extraSpecs;
445 if (transientStore.outputs.empty() ==
false) {
446 extraSpecs.push_back(transientStore);
448 if (qaStore.outputs.empty() ==
false) {
449 extraSpecs.push_back(qaStore);
452 if (aodSpawner.outputs.empty() ==
false) {
456 if (indexBuilder.outputs.empty() ==
false) {
457 extraSpecs.push_back(indexBuilder);
462 if (deploymentMode != DeploymentMode::OnlineDDS && deploymentMode != DeploymentMode::OnlineECS) {
463 if (analysisCCDBBackend.outputs.empty() ==
false) {
464 extraSpecs.push_back(analysisCCDBBackend);
468 auto tfnsource = std::ranges::find_if(workflow, [](
DataProcessorSpec const& spec) {
470 return DataSpecUtils::match(output,
"TFN",
"TFNumber", 0);
475 if (aodReader.outputs.empty() ==
false) {
476 if (tfnsource == workflow.end()) {
478 aodReader.outputs.emplace_back(
OutputSpec{
"TFN",
"TFNumber"});
479 aodReader.outputs.emplace_back(
OutputSpec{
"TFF",
"TFFilename"});
482 aodReader.algorithm = AlgorithmSpec{
484 [](DeviceSpec
const& spec) {
485 LOGP(warn,
"Workflow with injected AODs has unsatisfied inputs:");
486 for (
auto const&
output : spec.outputs) {
489 LOGP(fatal,
"Stopping.");
495 timer.outputs.emplace_back(concrete.origin, concrete.description, concrete.subSpec, Lifetime::Enumeration);
500 auto& dstf = std::get<ConcreteDataMatcher>(
matcher.matcher);
503 bool providesDISTSTF = std::ranges::any_of(workflow,
504 [&matcher](
auto const& dp) {
505 return std::any_of(dp.outputs.begin(), dp.outputs.end(), [&matcher](
auto const&
output) {
506 return DataSpecUtils::match(matcher, output);
513 bool requiresDISTSUBTIMEFRAME = std::ranges::any_of(workflow,
514 [&dstf](
auto const& dp) {
515 return std::any_of(dp.inputs.begin(), dp.inputs.end(), [&dstf](
auto const& input) {
516 return DataSpecUtils::match(input, dstf);
524 int enumCandidate = -1;
525 int timerCandidate = -1;
526 for (
auto wi = 0U; wi < workflow.size(); ++wi) {
527 auto& dp = workflow[wi];
528 if (dp.inputs.size() != 1) {
531 auto lifetime = dp.inputs[0].lifetime;
532 if (lifetime == Lifetime::Enumeration && (enumCandidate == -1 || workflow[enumCandidate].
name > dp.name)) {
535 if (lifetime == Lifetime::Timer && (timerCandidate == -1 || workflow[timerCandidate].
name > dp.name)) {
546 if (ccdbBackend.outputs.empty() ==
false) {
547 if (aodReader.outputs.empty() ==
false) {
549 ccdbBackend.inputs.push_back(
InputSpec{
"tfn",
"TFN",
"TFNumber"});
550 }
else if (providesDISTSTF) {
552 ccdbBackend.inputs.push_back(
InputSpec{
"tfn", dstf, Lifetime::Timeframe});
554 if (enumCandidate != -1) {
558 ccdbBackend.inputs.push_back(
InputSpec{
"tfn", dstf, Lifetime::Timeframe});
559 }
else if (timerCandidate != -1) {
562 ccdbBackend.inputs.push_back(
InputSpec{
"tfn", timer_dstf, Lifetime::Timeframe});
566 ccdbBackend.outputs.push_back(
OutputSpec{
"CTP",
"OrbitReset", 0});
570 }
else if (requiresDISTSUBTIMEFRAME && enumCandidate != -1) {
576 if (timer.outputs.empty() ==
false) {
577 extraSpecs.push_back(timer);
582 if (dec.providedOutputObjHist.empty() ==
false) {
584 extraSpecs.push_back(rootSink);
587 workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end());
590 injectAODWriter(workflow, ctx);
593 std::vector<InputSpec> redirectedOutputsInputs;
594 for (
auto ii = 0u; ii < dec.outputsInputs.size(); ii++) {
595 if (
ctx.options().get<std::string>(
"forwarding-policy") ==
"none") {
600 if (!dec.isDangling[ii] && (
ctx.options().get<std::string>(
"forwarding-policy") !=
"all")) {
607 redirectedOutputsInputs.emplace_back(dec.outputsInputs[ii]);
610 std::vector<InputSpec> unmatched;
611 auto forwardingDestination =
ctx.options().get<std::string>(
"forwarding-destination");
615 return DataSpecUtils::match(output,
"TFN",
"TFNumber", 0);
618 if (redirectedOutputsInputs.size() > 0 && forwardingDestination ==
"file") {
620 if (unmatched.size() != redirectedOutputsInputs.size()) {
621 extraSpecs.push_back(fileSink);
623 }
else if (redirectedOutputsInputs.size() > 0 && forwardingDestination ==
"fairmq") {
625 extraSpecs.push_back(fairMQSink);
626 }
else if (forwardingDestination !=
"drop") {
627 throw runtime_error_f(
"Unknown forwarding destination %s", forwardingDestination.c_str());
629 if ((unmatched.size() > 0) || (redirectedOutputsInputs.size() > 0) || (tfnsource != workflow.end())) {
630 std::vector<InputSpec> ignored = unmatched;
631 ignored.insert(ignored.end(), redirectedOutputsInputs.begin(), redirectedOutputsInputs.end());
632 for (
auto& ignoredInput : ignored) {
633 ignoredInput.lifetime = Lifetime::Sporadic;
638 if (tfnsource != workflow.end()) {
643 if (tfnsource != workflow.end() && !tfnsource->name.starts_with(
"aod-producer-workflow")) {
648 O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid,
"injectServiceDevices",
"Injecting rate limited dummy sink");
649 std::string rateLimitingChannelConfigOutput;
650 if (rateLimitingIPCID != -1) {
651 rateLimitingChannelConfigOutput = fmt::format(
"name=metric-feedback,type=push,method=bind,address=ipc://{}metric-feedback-{},transport=shmem,rateLogging=0",
ChannelSpecHelpers::defaultIPCFolder(), rateLimitingIPCID);
657 workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end());
780 std::vector<DeviceConnectionEdge>& logicalEdges,
781 std::vector<OutputSpec>& outputs,
782 std::vector<LogicalForwardInfo>& forwardedInputsInfo)
785 if (workflow.empty()) {
790 std::vector<LogicalOutputInfo> availableOutputsInfo;
791 auto const& constOutputs = outputs;
793 std::vector<LogicalOutputInfo> forwards;
797 auto enumerateAvailableOutputs = [&workflow, &outputs, &availableOutputsInfo]() {
799 for (
size_t wi = 0; wi < workflow.size(); ++wi) {
800 auto& producer = workflow[wi];
801 if (producer.outputs.empty()) {
802 O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid,
"output enumeration",
"No outputs for [%zu] %{public}s", wi, producer.name.c_str());
804 O2_SIGNPOST_START(workflow_helpers, sid,
"output enumeration",
"Enumerating outputs for producer [%zu] %{}s public", wi, producer.name.c_str());
806 for (
size_t oi = 0; oi < producer.outputs.size(); ++oi) {
807 auto& out = producer.outputs[oi];
808 auto uniqueOutputId = outputs.size();
812 outputs.push_back(out);
818 auto errorDueToMissingOutputFor = [&workflow, &constOutputs](
size_t ci,
size_t ii) {
819 auto input = workflow[ci].inputs[ii];
820 std::ostringstream
str;
821 str <<
"No matching output found for "
822 <<
DataSpecUtils::describe(input) <<
" as requested by data processor \"" << workflow[ci].name <<
"\". Candidates:\n";
824 for (
auto&
output : constOutputs) {
828 throw std::runtime_error(
str.str());
842 enumerateAvailableOutputs();
844 std::vector<bool> matches(constOutputs.size());
845 for (
size_t consumer = 0; consumer < workflow.size(); ++consumer) {
847 O2_SIGNPOST_START(workflow_helpers, sid,
"input matching",
"Matching inputs of consumer [%zu] %{}s public", consumer, workflow[consumer].
name.c_str());
848 for (
size_t input = 0; input < workflow[consumer].inputs.size(); ++input) {
850 for (
size_t i = 0;
i < constOutputs.size();
i++) {
859 for (
size_t i = 0;
i < availableOutputsInfo.size();
i++) {
861 if (!matches[availableOutputsInfo[
i].outputGlobalIndex]) {
864 auto* oif = &availableOutputsInfo[
i];
868 auto producer = oif->specIndex;
869 auto uniqueOutputId = oif->outputGlobalIndex;
870 for (
size_t tpi = 0; tpi < workflow[consumer].maxInputTimeslices; ++tpi) {
871 for (
size_t ptpi = 0; ptpi < workflow[producer].maxInputTimeslices; ++ptpi) {
872 O2_SIGNPOST_EVENT_EMIT(workflow_helpers, sid,
"output",
"Adding edge between %{public}s and %{public}s", workflow[consumer].
name.c_str(),
873 workflow[producer].name.c_str());
874 logicalEdges.emplace_back(
DeviceConnectionEdge{producer, consumer, tpi, ptpi, uniqueOutputId, input, oif->forward});
879 oif->enabled =
false;
881 if (forwards.empty()) {
882 errorDueToMissingOutputFor(consumer, input);
884 availableOutputsInfo.erase(std::remove_if(availableOutputsInfo.begin(), availableOutputsInfo.end(), [](
auto const& info) { return info.enabled == false; }), availableOutputsInfo.end());
885 std::ranges::copy(forwards, std::back_inserter(availableOutputsInfo));