Project
Loading...
Searching...
No Matches
DeviceSpecHelpers.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.
11#include "DeviceSpecHelpers.h"
13#include <wordexp.h>
14#include <algorithm>
15#include <boost/program_options.hpp>
16#include <cstdio>
17#include <cstdlib>
18#include <cstring>
19#include <string_view>
20#include <unordered_map>
21#include <unordered_set>
22#include <vector>
31#include "Framework/Lifetime.h"
37#include "Framework/Signpost.h"
41
42#include "WorkflowHelpers.h"
43
44#include <uv.h>
45#include <iostream>
46#include <fmt/format.h>
47
48#include <sys/time.h>
49#include <sys/resource.h>
50#include <csignal>
51#include <fairmq/Device.h>
52
53#include <regex>
54
55O2_DECLARE_DYNAMIC_LOG(device_spec_helpers);
56
57namespace bpo = boost::program_options;
58
59using namespace o2::framework;
60
61namespace o2::framework
62{
63
64namespace detail
65{
67{
68 // We simply wake up the event loop. Nothing to be done here.
69 auto* state = (DeviceState*)handle->data;
70 state->loopReason |= DeviceState::TIMER_EXPIRED;
71 state->loopReason |= DeviceState::DATA_INCOMING;
72 if (std::find(state->firedTimers.begin(), state->firedTimers.end(), handle) == state->firedTimers.end()) {
73 state->firedTimers.push_back(handle);
74 }
75}
76
78{
79 return [timer]() -> bool {
80 auto* state = (DeviceState*)timer->data;
81 return std::find(state->firedTimers.begin(), state->firedTimers.end(), timer) != state->firedTimers.end();
82 };
83}
84
86{
87 return [timer](uint64_t timeout_ms, uint64_t repeat_ms) -> void {
88 uv_timer_start(timer, detail::timer_callback, timeout_ms, repeat_ms);
89 };
90}
91
92void signal_callback(uv_signal_t* handle, int)
93{
94 // We simply wake up the event loop. Nothing to be done here.
95 auto* state = (DeviceState*)handle->data;
96 if (!state) {
97 return;
98 }
100 state->loopReason |= DeviceState::DATA_INCOMING;
101}
102} // namespace detail
103
109
111 {
112 return [matcher](DeviceState& state, ServiceRegistryRef, ConfigParamRegistry const& options) {
113 // A vector of all the available timer periods
114 std::vector<std::chrono::microseconds> periods;
115 // How long a ginven period should be active
116 std::vector<std::chrono::seconds> durations;
117 auto prefix = std::string{"period-"};
118 for (auto& meta : matcher.metadata) {
119 if (strncmp(meta.name.c_str(), prefix.c_str(), prefix.size()) == 0) {
120 // Parse the number after the prefix and consider it the duration
121 std::string_view duration(meta.name.c_str() + prefix.size(), meta.name.size() - prefix.size());
122 durations.emplace_back(std::chrono::seconds(std::stoi(std::string(duration))));
123 periods.emplace_back(std::chrono::microseconds(meta.defaultValue.get<uint64_t>() / 1000));
124 }
125 }
126 if (periods.empty()) {
127 std::string defaultRateName = std::string{"period-"} + matcher.binding;
128 auto defaultRate = std::chrono::milliseconds(options.get<int>(defaultRateName.c_str()));
129 O2_SIGNPOST_ID_GENERATE(tid, device_spec_helpers);
130 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, tid, "timeDrivenCreation", "Using default rate of %" PRIi64 " ms as specified by option period-%{public}s", defaultRate.count(),
131 matcher.binding.c_str());
132 periods.emplace_back(defaultRate.count());
133 durations.emplace_back(std::chrono::seconds((std::size_t)-1));
134 } else {
135 // If we have multiple periods, the last one gets the remaining interval
136 durations.back() = std::chrono::seconds((std::size_t)-1);
137 }
138 // We create a timer to wake us up. Notice the actual
139 // timeslot creation and record expiration still happens
140 // in a synchronous way.
141 auto* timer = (uv_timer_t*)(malloc(sizeof(uv_timer_t)));
142 timer->data = &state;
143 uv_timer_init(state.loop, timer);
144 uv_timer_start(timer, detail::timer_callback, periods.front().count(), periods.front().count());
145 state.activeTimers.push_back(timer);
146
148 };
149 }
150
157
158 static RouteConfigurator::CreationConfigurator signalDrivenConfigurator(InputSpec const& matcher, size_t inputTimeslice, size_t maxInputTimeslices)
159 {
160 return [matcher, inputTimeslice, maxInputTimeslices](DeviceState& state, ServiceRegistryRef, ConfigParamRegistry const& options) {
161 std::string startName = std::string{"start-value-"} + matcher.binding;
162 std::string endName = std::string{"end-value-"} + matcher.binding;
163 std::string stepName = std::string{"step-value-"} + matcher.binding;
164 auto start = options.get<int64_t>(startName.c_str());
165 auto stop = options.get<int64_t>(endName.c_str());
166 auto step = options.get<int64_t>(stepName.c_str());
167 // We create a timer to wake us up. Notice the actual
168 // timeslot creation and record expiration still happens
169 // in a synchronous way.
170 auto* sh = (uv_signal_t*)(malloc(sizeof(uv_signal_t)));
171 uv_signal_init(state.loop, sh);
172 sh->data = &state;
173 uv_signal_start(sh, detail::signal_callback, SIGUSR1);
174 state.activeSignals.push_back(sh);
175
176 return LifetimeHelpers::enumDrivenCreation(start, stop, step, inputTimeslice, maxInputTimeslices, 1);
177 };
178 }
179
186
187 static RouteConfigurator::CreationConfigurator enumDrivenConfigurator(InputSpec const& matcher, size_t inputTimeslice, size_t maxInputTimeslices)
188 {
189 return [matcher, inputTimeslice, maxInputTimeslices](DeviceState&, ServiceRegistryRef, ConfigParamRegistry const& options) {
190 std::string startName = std::string{"start-value-"} + matcher.binding;
191 std::string endName = std::string{"end-value-"} + matcher.binding;
192 std::string stepName = std::string{"step-value-"} + matcher.binding;
193 int64_t defaultStart = 0;
194 int64_t defaultStop = std::numeric_limits<int64_t>::max();
195 int64_t defaultStep = 1;
196 int defaultRepetitions = 1;
197 for (auto& meta : matcher.metadata) {
198 if (meta.name == "repetitions") {
199 defaultRepetitions = meta.defaultValue.get<int64_t>();
200 } else if (meta.name == "start-value") {
201 defaultStart = meta.defaultValue.get<int64_t>();
202 } else if (meta.name == "end-value") {
203 defaultStop = meta.defaultValue.get<int64_t>();
204 } else if (meta.name == "step-value") {
205 defaultStep = meta.defaultValue.get<int64_t>();
206 }
207 }
208 auto start = options.hasOption(startName.c_str()) ? options.get<int64_t>(startName.c_str()) : defaultStart;
209 auto stop = options.hasOption(endName.c_str()) ? options.get<int64_t>(endName.c_str()) : defaultStop;
210 auto step = options.hasOption(stepName.c_str()) ? options.get<int64_t>(stepName.c_str()) : defaultStep;
211 auto repetitions = defaultRepetitions;
212 return LifetimeHelpers::enumDrivenCreation(start, stop, step, inputTimeslice, maxInputTimeslices, repetitions);
213 };
214 }
215
220
225
227 {
228 return [](DeviceState&, ConfigParamRegistry const& options) {
229 auto serverUrl = options.get<std::string>("condition-backend");
230 return LifetimeHelpers::expectCTP(serverUrl, true);
231 };
232 }
233
234 static RouteConfigurator::ExpirationConfigurator expiringConditionConfigurator(InputSpec const& spec, std::string const& sourceChannel)
235 {
236 return [spec, sourceChannel](DeviceState&, ConfigParamRegistry const& options) {
237 auto serverUrl = options.get<std::string>("condition-backend");
238 auto forceTimestamp = options.get<std::string>("condition-timestamp");
239 return LifetimeHelpers::fetchFromCCDBCache(spec, serverUrl, forceTimestamp, sourceChannel);
240 };
241 }
242
243 static RouteConfigurator::CreationConfigurator fairmqDrivenConfiguration(InputSpec const& spec, int inputTimeslice, int maxInputTimeslices)
244 {
245 return [spec, inputTimeslice, maxInputTimeslices](DeviceState& state, ServiceRegistryRef services, ConfigParamRegistry const&) {
246 // std::string channelNameOption = std::string{"out-of-band-channel-name-"} + spec.binding;
247 // auto channelName = options.get<std::string>(channelNameOption.c_str());
248 std::string channelName = "upstream";
249 for (auto& meta : spec.metadata) {
250 if (meta.name != "channel-name") {
251 continue;
252 }
253 channelName = meta.defaultValue.get<std::string>();
254 }
255
256 auto device = services.get<RawDeviceService>().device();
257 auto& channel = device->GetChannels()[channelName];
258
259 // We assume there is always a ZeroMQ socket behind.
260 int zmq_fd = 0;
261 size_t zmq_fd_len = sizeof(zmq_fd);
262 auto* poller = (uv_poll_t*)malloc(sizeof(uv_poll_t));
263 channel[0].GetSocket().GetOption("fd", &zmq_fd, &zmq_fd_len);
264 if (zmq_fd == 0) {
265 throw runtime_error_f("Cannot get file descriptor for channel %s", channelName.c_str());
266 }
267 LOG(debug) << "Polling socket for " << channel[0].GetName();
268
269 state.activeOutOfBandPollers.push_back(poller);
270
271 // We always create entries whenever we get invoked.
272 // Notice this works only if we are the only input.
273 // Otherwise we should check the channel for new data,
274 // before we create an entry.
275 return LifetimeHelpers::enumDrivenCreation(0, -1, 1, inputTimeslice, maxInputTimeslices, 1);
276 };
277 }
278
285
287 {
288 return [spec](DeviceState&, ConfigParamRegistry const& options) {
289 std::string channelNameOption = std::string{"out-of-band-channel-name-"} + spec.binding;
290 auto channelName = options.get<std::string>(channelNameOption.c_str());
291 return LifetimeHelpers::fetchFromFairMQ(spec, channelName);
292 };
293 }
294
296 {
297 // FIXME: this should really be expireAlways. However, since we do not have
298 // a proper backend for conditions yet, I keep it behaving like it was
299 // before.
300 return [](DeviceState&, ConfigParamRegistry const&) { return LifetimeHelpers::expireNever(); };
301 }
302
307
309 {
310 return [matcher](DeviceState&, ConfigParamRegistry const&) {
312 };
313 }
314
316 {
317 return [matcher](DeviceState&, ConfigParamRegistry const&) {
319 };
320 }
321
322 static RouteConfigurator::ExpirationConfigurator expiringTimerConfigurator(InputSpec const& spec, std::string const& sourceChannel)
323 {
324 auto m = std::get_if<ConcreteDataMatcher>(&spec.matcher);
325 if (m == nullptr) {
326 throw runtime_error("InputSpec for Timers must be fully qualified");
327 }
328 // We copy the matcher to avoid lifetime issues.
329 return [matcher = *m, sourceChannel](DeviceState&, ConfigParamRegistry const&) {
330 // Timers do not have any orbit associated to them
331 return LifetimeHelpers::enumerate(matcher, sourceChannel, 0, 0);
332 };
333 }
334
335 static RouteConfigurator::ExpirationConfigurator expiringOOBConfigurator(InputSpec const& spec, std::string const& sourceChannel)
336 {
337 auto m = std::get_if<ConcreteDataMatcher>(&spec.matcher);
338 if (m == nullptr) {
339 throw runtime_error("InputSpec for OOB must be fully qualified");
340 }
341 // We copy the matcher to avoid lifetime issues.
342 return [matcher = *m, sourceChannel](DeviceState&, ConfigParamRegistry const&) {
343 // Timers do not have any orbit associated to them
344 return LifetimeHelpers::enumerate(matcher, sourceChannel, 0, 0);
345 };
346 }
347
348 static RouteConfigurator::ExpirationConfigurator expiringEnumerationConfigurator(InputSpec const& spec, std::string const& sourceChannel)
349 {
350 auto m = std::get_if<ConcreteDataMatcher>(&spec.matcher);
351 if (m == nullptr) {
352 throw runtime_error("InputSpec for Enumeration must be fully qualified");
353 }
354 // We copy the matcher to avoid lifetime issues.
355 return [matcher = *m, &spec, sourceChannel](DeviceState&, ConfigParamRegistry const& config) {
356 int defaultOrbitOffset = 0;
357 int defaultOrbitMultiplier = 1;
358 for (auto& meta : spec.metadata) {
359 if (meta.name == "orbit-offset") {
360 defaultOrbitOffset = meta.defaultValue.get<int64_t>();
361 } else if (meta.name == "orbit-multiplier") {
362 defaultOrbitMultiplier = meta.defaultValue.get<int64_t>();
363 }
364 }
365 size_t orbitOffset = config.hasOption("orbit-offset-enumeration") ? config.get<int64_t>("orbit-offset-enumeration") : defaultOrbitOffset;
366 size_t orbitMultiplier = config.hasOption("orbit-multiplier-enumeration") ? config.get<int64_t>("orbit-multiplier-enumeration") : defaultOrbitMultiplier;
367 return LifetimeHelpers::enumerate(matcher, sourceChannel, orbitOffset, orbitMultiplier);
368 };
369 }
370
372 {
373 // FIXME: this should really be expireAlways. However, since we do not have
374 // a proper backend for conditions yet, I keep it behaving like it was
375 // before.
376 return [](DeviceState&, ConfigParamRegistry const&) { return LifetimeHelpers::expireNever(); };
377 }
378
383
389
392 {
393 return [&routes](DeviceState&, ConfigParamRegistry const&) { return LifetimeHelpers::expireIfPresent(routes, ConcreteDataMatcher{"FLP", "DISTSUBTIMEFRAME", 0}); };
394 }
395
397 static RouteConfigurator::ExpirationConfigurator expiringOptionalConfigurator(InputSpec const& spec, std::string const& sourceChannel)
398 {
399 try {
401 return [concrete, sourceChannel](DeviceState&, ConfigParamRegistry const&) {
402 return LifetimeHelpers::dummy(concrete, sourceChannel);
403 };
404 } catch (...) {
406 ConcreteDataMatcher concrete{dataType.origin, dataType.description, 0xdeadbeef};
407 return [concrete, sourceChannel](DeviceState&, ConfigParamRegistry const&) {
408 return LifetimeHelpers::dummy(concrete, sourceChannel);
409 };
410 // We copy the matcher to avoid lifetime issues.
411 }
412 }
413};
414
418{
419 return fmt::format("{}type={},method={},address={},rateLogging={},rcvBufSize={},sndBufSize={}",
420 channel.name.empty() ? "" : "name=" + channel.name + ",",
424 channel.rateLogging,
425 channel.recvBufferSize,
426 channel.sendBufferSize);
427}
428
430{
431 return fmt::format("{}type={},method={},address={},rateLogging={},rcvBufSize={},sndBufSize={}",
432 channel.name.empty() ? "" : "name=" + channel.name + ",",
436 channel.rateLogging,
437 channel.recvBufferSize,
438 channel.sendBufferSize);
439}
440
441void DeviceSpecHelpers::validate(std::vector<DataProcessorSpec> const& workflow)
442{
443 // Iterate on all the DataProcessorSpecs in the altered_workflow
444 // and check for duplicates outputs among those who have lifetime == Timeframe
445 // Do so by:
446 //
447 // * Get the list of all Lifetime::Timeframe outputs for the workflow.
448 // Only those who are concrete matchers are considered for now, because
449 // it becomes to complicate to check for the wildcard case.
450 // * Sort the associated matchers by origin, description, subSpec
451 // * Check that the next element is not the same
452 std::vector<std::pair<int, std::string>> timeframeOutputs;
453 for (size_t i = 0; i < workflow.size(); ++i) {
454 auto& spec = workflow[i];
455 // We do not want to check for pipelining
456 if (spec.inputTimeSliceId != 0) {
457 continue;
458 }
459 for (auto& output : spec.outputs) {
460 if (output.lifetime != Lifetime::Timeframe) {
461 continue;
462 }
463 std::optional<ConcreteDataMatcher> matcher = DataSpecUtils::asOptionalConcreteDataMatcher(output);
464 if (!matcher) {
465 continue;
466 }
467 timeframeOutputs.emplace_back(i, DataSpecUtils::describe(*matcher));
468 }
469 }
470 std::stable_sort(timeframeOutputs.begin(), timeframeOutputs.end(), [](auto const& a, auto const& b) {
471 return a.second < b.second;
472 });
473
474 auto it = std::adjacent_find(timeframeOutputs.begin(), timeframeOutputs.end(), [](auto const& a, auto const& b) {
475 return a.second == b.second;
476 });
477 if (it != timeframeOutputs.end()) {
478 // Tell which are the two duplicates
479 auto device1 = workflow[it->first].name;
480 auto device2 = workflow[(it + 1)->first].name;
481 auto output1 = it->second;
482 auto output2 = (it + 1)->second;
483 throw std::runtime_error(fmt::format("Found duplicate outputs {} in device {} ({}) and {} in {} ({})",
484 output1, device1, it->first, output2, device2, (it + 1)->first));
485 }
486}
487
489 std::vector<DeviceSpec>& devices,
490 std::vector<DeviceId>& deviceIndex,
491 std::vector<DeviceConnectionId>& connections,
492 ResourceManager& resourceManager,
493 const std::vector<size_t>& outEdgeIndex,
494 const std::vector<DeviceConnectionEdge>& logicalEdges,
495 const std::vector<EdgeAction>& actions, const WorkflowSpec& workflow,
496 const std::vector<OutputSpec>& outputsMatchers,
497 const std::vector<ChannelConfigurationPolicy>& channelPolicies,
498 const std::vector<SendingPolicy>& sendingPolicies,
499 const std::vector<ForwardingPolicy>& forwardingPolicies,
500 std::string const& channelPrefix,
501 ComputingOffer const& defaultOffer,
502 OverrideServiceSpecs const& overrideServices)
503{
504 // The topology cannot be empty or not connected. If that is the case, than
505 // something before this went wrong.
506 // FIXME: is that really true???
507 assert(!workflow.empty());
508
509 // Edges are navigated in order for each device, so the device associaited to
510 // an edge is always the last one created.
511 auto deviceForEdge = [&actions, &workflow, &devices,
512 &logicalEdges, &resourceManager,
513 &defaultOffer, &channelPrefix, overrideServices](size_t ei, ComputingOffer& acceptedOffer) {
514 auto& edge = logicalEdges[ei];
515 auto& action = actions[ei];
516
517 if (action.requiresNewDevice == false) {
518 assert(devices.empty() == false);
519 return devices.size() - 1;
520 }
521 if (acceptedOffer.hostname != "") {
522 resourceManager.notifyAcceptedOffer(acceptedOffer);
523 }
524
525 auto& processor = workflow[edge.producer];
526
527 acceptedOffer.cpu = defaultOffer.cpu;
528 acceptedOffer.memory = defaultOffer.memory;
529 for (auto offer : resourceManager.getAvailableOffers()) {
530 if (offer.cpu < acceptedOffer.cpu) {
531 continue;
532 }
533 if (offer.memory < acceptedOffer.memory) {
534 continue;
535 }
536 acceptedOffer.hostname = offer.hostname;
537 acceptedOffer.startPort = offer.startPort;
538 acceptedOffer.rangeSize = 0;
539 break;
540 }
541
542 devices.emplace_back(DeviceSpec{
543 .name = processor.name,
544 .id = processor.maxInputTimeslices == 1 ? processor.name : processor.name + "_t" + std::to_string(edge.producerTimeIndex),
545 .channelPrefix = channelPrefix,
546 .inputChannels = {},
547 .options = processor.options,
548 .services = ServiceSpecHelpers::filterDisabled(processor.requiredServices, overrideServices),
549 .algorithm = processor.algorithm,
550 .rank = processor.rank,
551 .nSlots = processor.nSlots,
552 .inputTimesliceId = edge.producerTimeIndex,
553 .maxInputTimeslices = processor.maxInputTimeslices,
554 .resource = {acceptedOffer},
555 .labels = processor.labels,
556 .metadata = processor.metadata});
559 //
560 // for (auto& input : processor.inputs) {
561 // if (input.lifetime != Lifetime::OutOfBand) {
562 // continue;
563 // }
564 // InputChannelSpec extraInputChannelSpec{
565 // .name = "upstream",
566 // .type = ChannelType::Pair,
567 // .method = ChannelMethod::Bind,
568 // .hostname = "localhost",
569 // .port = 33000,
570 // .protocol = ChannelProtocol::IPC,
571 // };
572 // for (auto& meta : input.metadata) {
573 // if (meta.name == "name") {
574 // extraInputChannelSpec.name = meta.defaultValue.get<std::string>();
575 // }
576 // if (meta.name == "port") {
577 // extraInputChannelSpec.port = meta.defaultValue.get<int32_t>();
578 // }
579 // if (meta.name == "address") {
580 // extraInputChannelSpec.hostname = meta.defaultValue.get<std::string>();
581 // }
582 // }
583 // device.inputChannels.push_back(extraInputChannelSpec);
584 //}
585 for (auto& output : processor.outputs) {
586 if (output.lifetime != Lifetime::OutOfBand) {
587 continue;
588 }
589 OutputChannelSpec extraOutputChannelSpec{
590 .name = "downstream",
591 .type = ChannelType::Pair,
592 .method = ChannelMethod::Connect,
593 .hostname = "localhost",
594 .port = 33000,
595 .protocol = ChannelProtocol::IPC};
596 for (auto& meta : output.metadata) {
597 if (meta.name == "channel-name") {
598 extraOutputChannelSpec.name = meta.defaultValue.get<std::string>();
599 }
600 if (meta.name == "port") {
601 extraOutputChannelSpec.port = meta.defaultValue.get<int32_t>();
602 }
603 if (meta.name == "address") {
604 extraOutputChannelSpec.hostname = meta.defaultValue.get<std::string>();
605 }
606 }
607 devices.back().outputChannels.push_back(extraOutputChannelSpec);
608 }
609 return devices.size() - 1;
610 };
611
612 auto channelFromDeviceEdgeAndPort = [&connections, &workflow, &channelPolicies](const DeviceSpec& device,
613 ComputingResource& deviceResource,
614 ComputingOffer& acceptedOffer,
615 const DeviceConnectionEdge& edge) {
616 OutputChannelSpec channel;
617 auto& consumer = workflow[edge.consumer];
618 std::string consumerDeviceId = consumer.name;
619 if (consumer.maxInputTimeslices != 1) {
620 consumerDeviceId += "_t" + std::to_string(edge.timeIndex);
621 }
622 channel.name = device.channelPrefix + "from_" + device.id + "_to_" + consumerDeviceId;
623 channel.port = acceptedOffer.startPort + acceptedOffer.rangeSize;
624 channel.hostname = acceptedOffer.hostname;
625 deviceResource.usedPorts += 1;
626 acceptedOffer.rangeSize += 1;
627
628 for (auto& policy : channelPolicies) {
629 if (policy.match(device.id, consumerDeviceId)) {
630 policy.modifyOutput(channel);
631 break;
632 }
633 }
634 DeviceConnectionId id{edge.producer, edge.consumer, edge.timeIndex, edge.producerTimeIndex, channel.port};
635 connections.push_back(id);
636
637 auto& source = workflow[edge.producer];
638
639 O2_SIGNPOST_ID_GENERATE(sid, device_spec_helpers);
640 O2_SIGNPOST_START(device_spec_helpers, sid, "new channels", "Channel %{public}s has been created.", channel.name.c_str());
641 O2_SIGNPOST_ID_GENERATE(iid, device_spec_helpers);
642 O2_SIGNPOST_START(device_spec_helpers, iid, "producer outputs", "Producer %{public}s has the following outputs:", source.name.c_str());
643 for (auto& output : source.outputs) {
644 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, iid, "producer outputs", "%{public}s", DataSpecUtils::describe(output).c_str());
645 }
646 O2_SIGNPOST_END(device_spec_helpers, iid, "producer outputs", "");
647 O2_SIGNPOST_START(device_spec_helpers, iid, "producer forwards", "Producer %{public}s has the following forwards:", source.name.c_str());
648 for (auto& forwards : device.forwards) {
649 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, iid, "producer forwards", "%{public}s", DataSpecUtils::describe(forwards.matcher).c_str());
650 }
651 O2_SIGNPOST_END(device_spec_helpers, iid, "producer forwards", "");
652 O2_SIGNPOST_START(device_spec_helpers, iid, "consumer inputs", "Consumer %{public}s has the following inputs:", consumer.name.c_str());
653 for (auto& input : consumer.inputs) {
654 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, iid, "consumer inputs", "%{public}s", DataSpecUtils::describe(input).c_str());
655 }
656 O2_SIGNPOST_END(device_spec_helpers, iid, "consumer inputs", "");
657 O2_SIGNPOST_END(device_spec_helpers, sid, "new channels", "");
658 return channel;
659 };
660
661 auto isDifferentDestinationDeviceReferredBy = [&actions](size_t ei) { return actions[ei].requiresNewChannel; };
662
663 // This creates a new channel for a given edge, if needed. Notice that we
664 // navigate edges in a per device fashion (creating those if they are not
665 // alredy there) and create a new channel only if it connects two new
666 // devices. Whether or not this is the case was previously computed
667 // in the action.requiresNewChannel field.
668 auto createChannelForDeviceEdge = [&devices, &logicalEdges, &channelFromDeviceEdgeAndPort,
669 &deviceIndex](size_t di, size_t ei, ComputingOffer& offer) {
670 auto& device = devices[di];
671 auto& edge = logicalEdges[ei];
672
673 deviceIndex.emplace_back(DeviceId{edge.producer, edge.producerTimeIndex, di});
674
675 OutputChannelSpec channel = channelFromDeviceEdgeAndPort(device, device.resource, offer, edge);
676
677 device.outputChannels.push_back(channel);
678 return device.outputChannels.size() - 1;
679 };
680
681 // Notice how we need to behave in two different ways depending
682 // whether this is a real OutputRoute or if it's a forward from
683 // a previous consumer device.
684 // FIXME: where do I find the InputSpec for the forward?
685 auto appendOutputRouteToSourceDeviceChannel = [&outputsMatchers, &workflow, &devices, &logicalEdges, &sendingPolicies, &forwardingPolicies, &configContext](
686 size_t ei, size_t di, size_t ci) {
687 assert(ei < logicalEdges.size());
688 assert(di < devices.size());
689 assert(ci < devices[di].outputChannels.size());
690 auto& edge = logicalEdges[ei];
691 auto& device = devices[di];
692 assert(edge.consumer < workflow.size());
693 auto& consumer = workflow[edge.consumer];
694 auto& producer = workflow[edge.producer];
695 auto& channel = devices[di].outputChannels[ci];
696 assert(edge.outputGlobalIndex < outputsMatchers.size());
697 // Iterate over all the policies and apply the first one that matches.
698 SendingPolicy const* policyPtr = nullptr;
699 ForwardingPolicy const* forwardPolicyPtr = nullptr;
700 for (auto& policy : sendingPolicies) {
701 if (policy.matcher(producer, consumer, configContext)) {
702 policyPtr = &policy;
703 break;
704 }
705 }
706 assert(forwardingPolicies.empty() == false);
707 for (auto& policy : forwardingPolicies) {
708 if (policy.matcher(producer, consumer, configContext)) {
709 forwardPolicyPtr = &policy;
710 break;
711 }
712 }
713 assert(policyPtr != nullptr);
714 assert(forwardPolicyPtr != nullptr);
715
716 if (edge.isForward == false) {
717 OutputRoute route{
718 .timeslice = edge.timeIndex,
719 .maxTimeslices = consumer.maxInputTimeslices,
720 .matcher = outputsMatchers[edge.outputGlobalIndex],
721 .channel = channel.name,
722 .policy = policyPtr,
723 };
724 device.outputs.emplace_back(route);
725 } else {
726 ForwardRoute route{
727 .timeslice = edge.timeIndex,
728 .maxTimeslices = consumer.maxInputTimeslices,
729 .matcher = workflow[edge.consumer].inputs[edge.consumerInputIndex],
730 .channel = channel.name,
731 .policy = forwardPolicyPtr,
732 };
733 // In case we have a timer, the data it creates should be
734 // forwarded as a timeframe to the next device, so that
735 // we have synchronization.
736 if (route.matcher.lifetime == Lifetime::Timer) {
737 route.matcher.lifetime = Lifetime::Timeframe;
738 }
739 device.forwards.emplace_back(route);
740 }
741 };
742
743 auto sortDeviceIndex = [&deviceIndex]() { std::sort(deviceIndex.begin(), deviceIndex.end()); };
744
745 auto lastChannelFor = [&devices](size_t di) {
746 assert(di < devices.size());
747 assert(devices[di].outputChannels.empty() == false);
748 return devices[di].outputChannels.size() - 1;
749 };
750
751 //
752 // OUTER LOOP
753 //
754 // We need to create all the channels going out of a device, and associate
755 // routes to them for this reason
756 // we iterate over all the edges (which are per-datatype exchanged) and
757 // whenever we need to connect to a new device we create the channel. `device`
758 // here refers to the source device. This loop will therefore not create the
759 // devices which acts as sink, which are done in the preocessInEdgeActions
760 // function.
761 ComputingOffer acceptedOffer;
762 for (auto edge : outEdgeIndex) {
763 auto device = deviceForEdge(edge, acceptedOffer);
764 size_t channel = -1;
765 if (isDifferentDestinationDeviceReferredBy(edge)) {
766 channel = createChannelForDeviceEdge(device, edge, acceptedOffer);
767 } else {
768 channel = lastChannelFor(device);
769 }
770 appendOutputRouteToSourceDeviceChannel(edge, device, channel);
771 }
772 if (std::string(acceptedOffer.hostname) != "") {
773 resourceManager.notifyAcceptedOffer(acceptedOffer);
774 }
775 sortDeviceIndex();
776}
777
778void DeviceSpecHelpers::processInEdgeActions(std::vector<DeviceSpec>& devices,
779 std::vector<DeviceId>& deviceIndex,
780 const std::vector<DeviceConnectionId>& connections,
781 ResourceManager& resourceManager,
782 const std::vector<size_t>& inEdgeIndex,
783 const std::vector<DeviceConnectionEdge>& logicalEdges,
784 const std::vector<EdgeAction>& actions, const WorkflowSpec& workflow,
785 std::vector<LogicalForwardInfo> const& availableForwardsInfo,
786 std::vector<ChannelConfigurationPolicy> const& channelPolicies,
787 std::string const& channelPrefix,
788 ComputingOffer const& defaultOffer,
789 OverrideServiceSpecs const& overrideServices)
790{
791 auto const& constDeviceIndex = deviceIndex;
792 if (!std::is_sorted(constDeviceIndex.cbegin(), constDeviceIndex.cend())) {
793 throw o2::framework::runtime_error("Needs a sorted vector to be correct");
794 }
795
796 auto findProducerForEdge = [&logicalEdges, &constDeviceIndex](size_t ei) {
797 auto& edge = logicalEdges[ei];
798
799 DeviceId pid{edge.producer, edge.producerTimeIndex, 0};
800 auto deviceIt = std::lower_bound(constDeviceIndex.cbegin(), constDeviceIndex.cend(), pid);
801 // By construction producer should always be there
802 assert(deviceIt != constDeviceIndex.end());
803 assert(deviceIt->processorIndex == pid.processorIndex && deviceIt->timeslice == pid.timeslice);
804 return deviceIt->deviceIndex;
805 };
806
807 auto findConsumerForEdge = [&logicalEdges, &constDeviceIndex](size_t ei) {
808 auto& edge = logicalEdges[ei];
809
810 DeviceId pid{edge.consumer, edge.timeIndex, 0};
811 auto deviceIt = std::lower_bound(constDeviceIndex.cbegin(), constDeviceIndex.cend(), pid);
812 // We search for a consumer only if we know it's is already there.
813 assert(deviceIt != constDeviceIndex.end());
814 assert(deviceIt->processorIndex == pid.processorIndex && deviceIt->timeslice == pid.timeslice);
815 return deviceIt->deviceIndex;
816 };
817
818 // Notice that to start with, consumer exists only if they also are
819 // producers, so we need to create one if it does not exist. Given this is
820 // stateful, we keep an eye on what edge was last searched to make sure we
821 // are not screwing up.
822 //
823 // Notice this is not thread safe.
824 decltype(deviceIndex.begin()) lastConsumerSearch;
825 size_t lastConsumerSearchEdge;
826 auto hasConsumerForEdge = [&lastConsumerSearch, &lastConsumerSearchEdge, &deviceIndex,
827 &logicalEdges](size_t ei) -> int {
828 auto& edge = logicalEdges[ei];
829 DeviceId cid{edge.consumer, edge.timeIndex, 0};
830 lastConsumerSearchEdge = ei; // This will invalidate the cache
831 lastConsumerSearch = std::lower_bound(deviceIndex.begin(), deviceIndex.end(), cid);
832 return lastConsumerSearch != deviceIndex.end() && cid.processorIndex == lastConsumerSearch->processorIndex &&
833 cid.timeslice == lastConsumerSearch->timeslice;
834 };
835
836 // The passed argument is there just to check. We do know that the last searched
837 // is the one we want.
838 auto getConsumerForEdge = [&lastConsumerSearch, &lastConsumerSearchEdge](size_t ei) {
839 assert(ei == lastConsumerSearchEdge);
840 return lastConsumerSearch->deviceIndex;
841 };
842
843 auto createNewDeviceForEdge = [&workflow, &logicalEdges, &devices,
844 &deviceIndex, &resourceManager, &defaultOffer,
845 &channelPrefix, &overrideServices](size_t ei, ComputingOffer& acceptedOffer) {
846 auto& edge = logicalEdges[ei];
847
848 if (acceptedOffer.hostname != "") {
849 resourceManager.notifyAcceptedOffer(acceptedOffer);
850 }
851
852 auto& processor = workflow[edge.consumer];
853
854 acceptedOffer.cpu = defaultOffer.cpu;
855 acceptedOffer.memory = defaultOffer.memory;
856 for (auto offer : resourceManager.getAvailableOffers()) {
857 if (offer.cpu < acceptedOffer.cpu) {
858 continue;
859 }
860 if (offer.memory < acceptedOffer.memory) {
861 continue;
862 }
863 acceptedOffer.hostname = offer.hostname;
864 acceptedOffer.startPort = offer.startPort;
865 acceptedOffer.rangeSize = 0;
866 break;
867 }
868
869 DeviceSpec device{
870 .name = processor.name,
871 .id = processor.name,
872 .channelPrefix = channelPrefix,
873 .options = processor.options,
874 .services = ServiceSpecHelpers::filterDisabled(processor.requiredServices, overrideServices),
875 .algorithm = processor.algorithm,
876 .rank = processor.rank,
877 .nSlots = processor.nSlots,
878 .inputTimesliceId = edge.timeIndex,
879 .maxInputTimeslices = processor.maxInputTimeslices,
880 .resource = {acceptedOffer},
881 .labels = processor.labels,
882 .metadata = processor.metadata};
883
884 if (processor.maxInputTimeslices != 1) {
885 device.id += "_t" + std::to_string(edge.timeIndex);
886 }
887
888 // FIXME: maybe I should use an std::map in the end
889 // but this is really not performance critical
890 auto id = DeviceId{edge.consumer, edge.timeIndex, devices.size()};
891 devices.emplace_back(std::move(device));
892 deviceIndex.push_back(id);
893 std::sort(deviceIndex.begin(), deviceIndex.end());
894 return devices.size() - 1;
895 };
896
897 // We search for a preexisting outgoing connection associated to this edge.
898 // This is to retrieve the port of the source.
899 // This has to exists, because we already created all the outgoing connections
900 // so it's just a matter of looking it up.
901 auto findMatchingOutgoingPortForEdge = [&logicalEdges, &connections](size_t ei) {
902 auto const& edge = logicalEdges[ei];
903 DeviceConnectionId connectionId{edge.producer, edge.consumer, edge.timeIndex, edge.producerTimeIndex, 0};
904
905 auto it = std::lower_bound(connections.begin(), connections.end(), connectionId);
906
907 assert(it != connections.end());
908 assert(it->producer == connectionId.producer);
909 assert(it->consumer == connectionId.consumer);
910 assert(it->timeIndex == connectionId.timeIndex);
911 assert(it->producerTimeIndex == connectionId.producerTimeIndex);
912 return it->port;
913 };
914
915 auto checkNoDuplicatesFor = [](std::vector<InputChannelSpec> const& channels, const std::string& name) {
916 for (auto const& channel : channels) {
917 if (channel.name == name) {
918 return false;
919 }
920 }
921 return true;
922 };
923 auto appendInputChannelForConsumerDevice = [&devices, &checkNoDuplicatesFor, &channelPolicies](
924 size_t pi, size_t ci, unsigned short port) {
925 auto const& producerDevice = devices[pi];
926 auto& consumerDevice = devices[ci];
927 InputChannelSpec channel;
928 channel.name = producerDevice.channelPrefix + "from_" + producerDevice.id + "_to_" + consumerDevice.id;
929 channel.hostname = producerDevice.resource.hostname;
930 channel.port = port;
931 for (auto& policy : channelPolicies) {
932 if (policy.match(producerDevice.id, consumerDevice.id)) {
933 policy.modifyInput(channel);
934 break;
935 }
936 }
937 assert(checkNoDuplicatesFor(consumerDevice.inputChannels, channel.name));
938 consumerDevice.inputChannels.push_back(channel);
939 return consumerDevice.inputChannels.size() - 1;
940 };
941
942 // I think this is trivial, since I think it should always be the last one,
943 // in case it's not actually the case, I should probably do an actual lookup
944 // here.
945 auto getChannelForEdge = [&devices](size_t pi, size_t ci) {
946 auto& consumerDevice = devices[ci];
947 return consumerDevice.inputChannels.size() - 1;
948 };
949
950 // This is always called when adding a new channel, so we can simply refer
951 // to back. Notice also that this is the place where it makes sense to
952 // assign the forwarding, given that the forwarded stuff comes from some
953 // input.
954 auto appendInputRouteToDestDeviceChannel = [&devices, &logicalEdges, &workflow](size_t ei, size_t di, size_t ci) {
955 auto const& edge = logicalEdges[ei];
956 auto const& consumer = workflow[edge.consumer];
957 auto const& producer = workflow[edge.producer];
958 auto& consumerDevice = devices[di];
959
960 auto const& inputSpec = consumer.inputs[edge.consumerInputIndex];
961 auto const& sourceChannel = consumerDevice.inputChannels[ci].name;
962
963 InputRoute route{
964 inputSpec,
965 edge.consumerInputIndex,
966 sourceChannel,
967 edge.producerTimeIndex,
968 std::nullopt};
969
970 // In case we have wildcards, we must make sure that some other edge
971 // produced the same route, i.e. has the same matcher. Without this,
972 // otherwise, we would end up with as many input routes as the outputs that
973 // can be matched by the wildcard.
974 for (size_t iri = 0; iri < consumerDevice.inputs.size(); ++iri) {
975 auto& existingRoute = consumerDevice.inputs[iri];
976 if (existingRoute.timeslice != edge.producerTimeIndex) {
977 continue;
978 }
979 if (existingRoute.inputSpecIndex == edge.consumerInputIndex) {
980 return;
981 }
982 }
983
984 // In case we add a new route to the device, we remap any
985 // Lifetime::Timer to Lifetime::Timeframe, so that we can
986 // synchronize the devices without creating a new timer.
987 if (edge.isForward && route.matcher.lifetime == Lifetime::Timer) {
988 LOGP(warn,
989 "Warning: Forwarding timer {} from {} to a {} as both requested it."
990 " If this is undesired, please make sure to use two different data matchers for their InputSpec.",
991 DataSpecUtils::describe(route.matcher).c_str(),
992 producer.name.c_str(),
993 consumer.name.c_str());
994 route.matcher.lifetime = Lifetime::Timeframe;
995 }
996
997 consumerDevice.inputs.push_back(route);
998 };
999
1000 // Outer loop. A new device is needed for each
1001 // of the sink data processors.
1002 // New InputChannels need to refer to preexisting OutputChannels we create
1003 // previously.
1004 ComputingOffer acceptedOffer;
1005 for (size_t edge : inEdgeIndex) {
1006 auto& action = actions[edge];
1007
1008 size_t consumerDevice = -1;
1009
1010 if (action.requiresNewDevice) {
1011 if (hasConsumerForEdge(edge)) {
1012 consumerDevice = getConsumerForEdge(edge);
1013 } else {
1014 consumerDevice = createNewDeviceForEdge(edge, acceptedOffer);
1015 }
1016 } else {
1017 consumerDevice = findConsumerForEdge(edge);
1018 }
1019 size_t producerDevice = findProducerForEdge(edge);
1020
1021 size_t channel = -1;
1022 if (action.requiresNewChannel) {
1023 int16_t port = findMatchingOutgoingPortForEdge(edge);
1024 channel = appendInputChannelForConsumerDevice(producerDevice, consumerDevice, port);
1025 } else {
1026 channel = getChannelForEdge(producerDevice, consumerDevice);
1027 }
1028 appendInputRouteToDestDeviceChannel(edge, consumerDevice, channel);
1029 }
1030
1031 // Bind the expiration mechanism to the input routes
1032 for (auto& device : devices) {
1033 for (auto& route : device.inputs) {
1034 switch (route.matcher.lifetime) {
1035 case Lifetime::OutOfBand:
1036 route.configurator = {
1037 .name = "oob",
1038 .creatorConfigurator = ExpirationHandlerHelpers::loopEventDrivenConfigurator(route.matcher),
1040 .expirationConfigurator = ExpirationHandlerHelpers::expiringOOBConfigurator(route.matcher, route.sourceChannel)};
1041 break;
1042 // case Lifetime::Condition:
1043 // route.configurator = {
1044 // ExpirationHandlerHelpers::dataDrivenConfigurator(),
1045 // ExpirationHandlerHelpers::danglingConditionConfigurator(),
1046 // ExpirationHandlerHelpers::expiringConditionConfigurator(inputSpec, sourceChannel)};
1047 // break;
1048 case Lifetime::QA:
1049 route.configurator = {
1050 .name = "qa",
1051 .creatorConfigurator = ExpirationHandlerHelpers::dataDrivenConfigurator(),
1052 .danglingConfigurator = ExpirationHandlerHelpers::danglingQAConfigurator(),
1053 .expirationConfigurator = ExpirationHandlerHelpers::expiringQAConfigurator()};
1054 break;
1055 case Lifetime::Timer:
1056 route.configurator = {
1057 .name = "timer",
1058 .creatorConfigurator = ExpirationHandlerHelpers::timeDrivenConfigurator(route.matcher),
1059 .danglingConfigurator = ExpirationHandlerHelpers::danglingTimerConfigurator(route.matcher),
1060 .expirationConfigurator = ExpirationHandlerHelpers::expiringTimerConfigurator(route.matcher, route.sourceChannel)};
1061 break;
1062 case Lifetime::Enumeration:
1063 route.configurator = {
1064 .name = "enumeration",
1065 .creatorConfigurator = ExpirationHandlerHelpers::enumDrivenConfigurator(route.matcher, device.inputTimesliceId, device.maxInputTimeslices),
1066 .danglingConfigurator = ExpirationHandlerHelpers::danglingEnumerationConfigurator(route.matcher),
1067 .expirationConfigurator = ExpirationHandlerHelpers::expiringEnumerationConfigurator(route.matcher, route.sourceChannel)};
1068 break;
1069 case Lifetime::Signal:
1070 route.configurator = {
1071 .name = "signal",
1072 .creatorConfigurator = ExpirationHandlerHelpers::signalDrivenConfigurator(route.matcher, device.inputTimesliceId, device.maxInputTimeslices),
1073 .danglingConfigurator = ExpirationHandlerHelpers::danglingEnumerationConfigurator(route.matcher),
1074 .expirationConfigurator = ExpirationHandlerHelpers::expiringEnumerationConfigurator(route.matcher, route.sourceChannel)};
1075 break;
1076 case Lifetime::Transient:
1077 route.configurator = {
1078 .name = "transient",
1079 .creatorConfigurator = ExpirationHandlerHelpers::dataDrivenConfigurator(),
1081 .expirationConfigurator = ExpirationHandlerHelpers::expiringTransientConfigurator(route.matcher)};
1082 break;
1083 case Lifetime::Optional:
1084 route.configurator = {
1085 .name = "optional",
1087 .danglingConfigurator = ExpirationHandlerHelpers::danglingOptionalConfigurator(device.inputs),
1088 .expirationConfigurator = ExpirationHandlerHelpers::expiringOptionalConfigurator(route.matcher, route.sourceChannel)};
1089 break;
1090 default:
1091 break;
1092 }
1093 }
1094 }
1095
1096 if (acceptedOffer.hostname != "") {
1097 resourceManager.notifyAcceptedOffer(acceptedOffer);
1098 }
1099}
1100
1101// Construct the list of actual devices we want, given a workflow.
1102//
1103// FIXME: make start port configurable?
1105 std::vector<ChannelConfigurationPolicy> const& channelPolicies,
1106 std::vector<CompletionPolicy> const& completionPolicies,
1107 std::vector<DispatchPolicy> const& dispatchPolicies,
1108 std::vector<ResourcePolicy> const& resourcePolicies,
1109 std::vector<CallbacksPolicy> const& callbacksPolicies,
1110 std::vector<SendingPolicy> const& sendingPolicies,
1111 std::vector<ForwardingPolicy> const& forwardingPolicies,
1112 std::vector<DeviceSpec>& devices,
1113 ResourceManager& resourceManager,
1114 std::string const& uniqueWorkflowId,
1115 ConfigContext const& configContext,
1116 bool optimizeTopology,
1117 unsigned short resourcesMonitoringInterval,
1118 std::string const& channelPrefix,
1119 OverrideServiceSpecs const& overrideServices)
1120{
1121 // Always check for validity of the workflow before instanciating it
1123 // In case the workflow is empty, we simply do not need to instanciate any device.
1124 if (workflow.empty()) {
1125 return;
1126 }
1127 std::vector<LogicalForwardInfo> availableForwardsInfo;
1128 std::vector<DeviceConnectionEdge> logicalEdges;
1129 std::vector<DeviceConnectionId> connections;
1130 std::vector<DeviceId> deviceIndex;
1131
1132 // This is a temporary store for inputs and outputs,
1133 // including forwarded channels, so that we can construct
1134 // them before assigning to a device.
1135 std::vector<OutputSpec> outputs;
1136
1137 WorkflowHelpers::constructGraph(workflow, logicalEdges, outputs, availableForwardsInfo);
1138
1139 // We need to instanciate one device per (me, timeIndex) in the
1140 // DeviceConnectionEdge. For each device we need one new binding
1141 // server per (me, other) -> port Moreover for each (me, other,
1142 // outputGlobalIndex) we need to insert either an output or a
1143 // forward.
1144 //
1145 // We then sort by other. For each (other, me) we need to connect to
1146 // port (me, other) and add an input.
1147
1148 // Fill an index to do the sorting
1149 std::vector<size_t> inEdgeIndex;
1150 std::vector<size_t> outEdgeIndex;
1151 WorkflowHelpers::sortEdges(inEdgeIndex, outEdgeIndex, logicalEdges);
1152
1153 std::vector<EdgeAction> outActions = WorkflowHelpers::computeOutEdgeActions(logicalEdges, outEdgeIndex);
1154 // Crete the connections on the inverse map for all of them
1155 // lookup for port and add as input of the current device.
1156 std::vector<EdgeAction> inActions = WorkflowHelpers::computeInEdgeActions(logicalEdges, inEdgeIndex);
1157 size_t deviceCount = 0;
1158 for (auto& action : outActions) {
1159 deviceCount += action.requiresNewDevice ? 1 : 0;
1160 }
1161 for (auto& action : inActions) {
1162 deviceCount += action.requiresNewDevice ? 1 : 0;
1163 }
1164
1165 ComputingOffer defaultOffer;
1166 for (auto& offer : resourceManager.getAvailableOffers()) {
1167 defaultOffer.cpu += offer.cpu;
1168 defaultOffer.memory += offer.memory;
1169 }
1170
1172 defaultOffer.cpu /= deviceCount + 1;
1173 defaultOffer.memory /= deviceCount + 1;
1174
1175 processOutEdgeActions(configContext, devices, deviceIndex, connections, resourceManager, outEdgeIndex, logicalEdges,
1176 outActions, workflow, outputs, channelPolicies, sendingPolicies, forwardingPolicies, channelPrefix, defaultOffer, overrideServices);
1177
1178 // FIXME: is this not the case???
1179 std::sort(connections.begin(), connections.end());
1180
1181 processInEdgeActions(devices, deviceIndex, connections, resourceManager, inEdgeIndex, logicalEdges,
1182 inActions, workflow, availableForwardsInfo, channelPolicies, channelPrefix, defaultOffer, overrideServices);
1183 // We apply the completion policies here since this is where we have all the
1184 // devices resolved.
1185 std::map<std::string, DataProcessorPoliciesInfo> policies;
1186 for (DeviceSpec& device : devices) {
1187 bool hasPolicy = false;
1188 policies[device.name].completionPolicyName = "unknown";
1189 for (auto& policy : completionPolicies) {
1190 if (policy.matcher(device) == true) {
1191 policies[policy.name].completionPolicyName = policy.name;
1192 device.completionPolicy = policy;
1193 hasPolicy = true;
1194 break;
1195 }
1196 }
1197 if (hasPolicy == false) {
1198 throw runtime_error_f("Unable to find a completion policy for %s", device.id.c_str());
1199 }
1200 for (auto& policy : dispatchPolicies) {
1201 if (policy.deviceMatcher(device) == true) {
1202 device.dispatchPolicy = policy;
1203 break;
1204 }
1205 }
1206 for (auto& policy : callbacksPolicies) {
1207 if (policy.matcher(device, configContext) == true) {
1208 device.callbacksPolicy = policy;
1209 break;
1210 }
1211 }
1212 hasPolicy = false;
1213 for (auto& policy : resourcePolicies) {
1214 if (policy.matcher(device) == true) {
1215 device.resourcePolicy = policy;
1216 hasPolicy = true;
1217 break;
1218 }
1219 }
1220 if (hasPolicy == false) {
1221 throw runtime_error_f("Unable to find a resource policy for %s", device.id.c_str());
1222 }
1223 }
1224 // Iterate of the workflow and create a consistent vector of DataProcessorPoliciesInfo
1225 std::vector<DataProcessorPoliciesInfo> policiesVector;
1226 for (size_t wi = 0; wi < workflow.size(); ++wi) {
1227 auto& processor = workflow[wi];
1228 auto& info = policies[processor.name];
1229 policiesVector.push_back(info);
1230 }
1231
1232 WorkflowHelpers::validateEdges(workflow, policiesVector, logicalEdges, outputs);
1233
1234 for (auto& device : devices) {
1235 device.resourceMonitoringInterval = resourcesMonitoringInterval;
1236 }
1237
1238 auto findDeviceIndex = [&deviceIndex](size_t processorIndex, size_t timeslice) {
1239 for (auto& deviceEdge : deviceIndex) {
1240 if (deviceEdge.processorIndex != processorIndex) {
1241 continue;
1242 }
1243 if (deviceEdge.timeslice != timeslice) {
1244 continue;
1245 }
1246 return deviceEdge.deviceIndex;
1247 }
1248 throw runtime_error("Unable to find device.");
1249 };
1250
1251 // Optimize the topology when two devices are
1252 // running on the same node.
1253 if (optimizeTopology) {
1254 for (auto& connection : connections) {
1255 auto& device1 = devices[findDeviceIndex(connection.consumer, connection.timeIndex)];
1256 auto& device2 = devices[findDeviceIndex(connection.producer, connection.producerTimeIndex)];
1257 // No need to do anything if they are not on the same host
1258 if (device1.resource.hostname != device2.resource.hostname) {
1259 continue;
1260 }
1261 for (auto& input : device1.inputChannels) {
1262 for (auto& output : device2.outputChannels) {
1263 if (input.hostname == output.hostname && input.port == output.port) {
1264 input.protocol = ChannelProtocol::IPC;
1265 output.protocol = ChannelProtocol::IPC;
1266 input.hostname += uniqueWorkflowId;
1267 output.hostname += uniqueWorkflowId;
1268 }
1269 }
1270 }
1271 }
1272 }
1273}
1274
1275void DeviceSpecHelpers::reworkHomogeneousOption(std::vector<DataProcessorInfo>& infos, char const* name, char const* defaultValue)
1276{
1277 std::string finalValue;
1278 for (auto& info : infos) {
1279 auto it = std::find(info.cmdLineArgs.begin(), info.cmdLineArgs.end(), name);
1280 if (it == info.cmdLineArgs.end()) {
1281 continue;
1282 }
1283 auto value = it + 1;
1284 if (value == info.cmdLineArgs.end()) {
1285 throw runtime_error_f("%s requires an argument", name);
1286 }
1287 if (!finalValue.empty() && finalValue != *value) {
1288 throw runtime_error_f("Found incompatible %s values: %s amd %s", name, finalValue.c_str(), value->c_str());
1289 }
1290 finalValue = *value;
1291 info.cmdLineArgs.erase(it, it + 2);
1292 }
1293 if (finalValue.empty() && defaultValue == nullptr) {
1294 return;
1295 }
1296 if (finalValue.empty()) {
1297 finalValue = defaultValue;
1298 }
1299 for (auto& info : infos) {
1300 info.cmdLineArgs.emplace_back(name);
1301 info.cmdLineArgs.push_back(finalValue);
1302 }
1303}
1304
1305void DeviceSpecHelpers::reworkIntegerOption(std::vector<DataProcessorInfo>& infos, char const* name, std::function<long long()> defaultValueCallback, long long startValue, std::function<long long(long long, long long)> bestValue)
1306{
1307 int64_t finalValue = startValue;
1308 bool wasModified = false;
1309 for (auto& info : infos) {
1310 auto it = std::find(info.cmdLineArgs.begin(), info.cmdLineArgs.end(), name);
1311 if (it == info.cmdLineArgs.end()) {
1312 continue;
1313 }
1314 auto valueS = it + 1;
1315 if (valueS == info.cmdLineArgs.end()) {
1316 throw runtime_error_f("%s requires an integer argument", name);
1317 }
1318 char* err = nullptr;
1319 long long value = strtoll(valueS->c_str(), &err, 10);
1320 finalValue = bestValue(value, finalValue);
1321 wasModified = true;
1322 info.cmdLineArgs.erase(it, it + 2);
1323 }
1324 if (!wasModified && defaultValueCallback == nullptr) {
1325 return;
1326 }
1327 if (!wasModified) {
1328 finalValue = defaultValueCallback();
1329 }
1330 for (auto& info : infos) {
1331 info.cmdLineArgs.emplace_back(name);
1332 info.cmdLineArgs.push_back(std::to_string(finalValue));
1333 }
1334}
1335
1336void DeviceSpecHelpers::reworkShmSegmentSize(std::vector<DataProcessorInfo>& infos)
1337{
1338 int64_t segmentSize = 0;
1339 for (auto& info : infos) {
1340 auto it = std::find(info.cmdLineArgs.begin(), info.cmdLineArgs.end(), "--shm-segment-size");
1341 if (it == info.cmdLineArgs.end()) {
1342 continue;
1343 }
1344 auto value = it + 1;
1345 if (value == info.cmdLineArgs.end()) {
1346 throw runtime_error("--shm-segment-size requires an argument");
1347 }
1348 char* err = nullptr;
1349 int64_t size = strtoll(value->c_str(), &err, 10);
1350 if (size > segmentSize) {
1351 segmentSize = size;
1352 }
1353 info.cmdLineArgs.erase(it, it + 2);
1354 }
1356 if (segmentSize == 0) {
1357 struct rlimit limits;
1358 getrlimit(RLIMIT_AS, &limits);
1359 if (limits.rlim_cur != RLIM_INFINITY) {
1360 segmentSize = std::min(limits.rlim_cur - 1000000000LL, (limits.rlim_cur * 90LL) / 100LL);
1361 }
1362 }
1363 if (segmentSize == 0) {
1364 segmentSize = 2000000000LL;
1365 }
1366 for (auto& info : infos) {
1367 info.cmdLineArgs.emplace_back("--shm-segment-size");
1368 info.cmdLineArgs.push_back(std::to_string(segmentSize));
1369 }
1370}
1371
1372namespace
1373{
1374template <class Container>
1375void split(const std::string& str, Container& cont)
1376{
1377 std::istringstream iss(str);
1378 std::copy(std::istream_iterator<std::string>(iss),
1379 std::istream_iterator<std::string>(),
1380 std::back_inserter(cont));
1381}
1382} // namespace
1383
1384void DeviceSpecHelpers::prepareArguments(bool defaultQuiet, bool defaultStopped, bool interactive,
1385 unsigned short driverPort,
1386 o2::framework::DriverConfig const& driverConfig,
1387 std::vector<DataProcessorInfo> const& processorInfos,
1388 std::vector<DeviceSpec> const& deviceSpecs,
1389 std::vector<DeviceExecution>& deviceExecutions,
1390 std::vector<DeviceControl>& deviceControls,
1391 std::vector<ConfigParamSpec> const& detectedOptions,
1392 std::string const& uniqueWorkflowId)
1393{
1394 assert(deviceSpecs.size() == deviceExecutions.size());
1395 assert(deviceControls.size() == deviceExecutions.size());
1396 for (size_t si = 0; si < deviceSpecs.size(); ++si) {
1397 auto& spec = deviceSpecs[si];
1398 O2_SIGNPOST_ID_GENERATE(poid, device_spec_helpers);
1399 O2_SIGNPOST_START(device_spec_helpers, poid, "prepareArguments", "Preparing options for %{public}s", spec.id.c_str());
1400 auto& control = deviceControls[si];
1401 auto& execution = deviceExecutions[si];
1402
1403 control.quiet = defaultQuiet;
1404 control.stopped = defaultStopped;
1405
1406 int argc;
1407 char** argv;
1408 // We need to start with the detected options, so that they are not lost.
1409 // Notice how detected options can be detected at any moment in the chain,
1410 // so it's important that if you rely on them, they get passed on
1411 // always.
1412 std::vector<ConfigParamSpec> workflowOptions = detectedOptions;
1413 for (auto& opt : detectedOptions) {
1414 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, poid, "prepareArguments", "Processor option %{public}s passed as previously detected", opt.name.c_str());
1415 }
1419 auto pi = std::find_if(processorInfos.begin(), processorInfos.end(), [&](auto const& x) { return x.name == spec.id; });
1420 argc = pi->cmdLineArgs.size() + 1;
1421 argv = (char**)malloc(sizeof(char**) * (argc + 1));
1422 argv[0] = strdup(pi->executable.data());
1423 for (size_t ai = 0; ai < pi->cmdLineArgs.size(); ++ai) {
1424 auto const& arg = pi->cmdLineArgs[ai];
1425 argv[ai + 1] = strdup(arg.data());
1426 }
1427 argv[argc] = nullptr;
1428 for (auto& opt : pi->workflowOptions) {
1429 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, poid, "prepareArguments", "Processor option %{public}s found in process description", opt.name.c_str());
1430 workflowOptions.push_back(opt);
1431 }
1432 std::sort(workflowOptions.begin(), workflowOptions.end(), [](ConfigParamSpec const& a, ConfigParamSpec const& b) { return a.name < b.name; });
1433 auto last = std::unique(workflowOptions.begin(), workflowOptions.end());
1434 workflowOptions.erase(last, workflowOptions.end());
1435
1436 for (auto& opt : workflowOptions) {
1437 O2_SIGNPOST_EVENT_EMIT(device_spec_helpers, poid, "prepareArguments", "Final unique option %{public}s added to list of workflowOptions", opt.name.c_str());
1438 }
1439 // We duplicate the list of options, filtering only those
1440 // which are actually relevant for the given device. The additional
1441 // four are to add
1442 // * name of the executable
1443 // * --framework-id <id> so that we can use the workflow
1444 // executable also in other context where we do not fork, e.g. DDS.
1445 // * final NULL required by execvp
1446 //
1447 // We do it here because we are still in the parent and we can therefore
1448 // capture them to be displayed in the GUI or to populate the DDS configuration
1449 // to dump
1450
1451 // Set up options for the device running underneath
1452 // FIXME: add some checksum in framework id. We could use this
1453 // to avoid redeploys when only a portion of the workflow is changed.
1454 // FIXME: this should probably be done in one go with char *, but I am lazy.
1455 std::vector<std::string> tmpArgs = {argv[0],
1456 "--id", spec.id.c_str(),
1457 "--control", interactive ? "gui" : "static",
1458 "--shm-monitor", "false",
1459 "--log-color", "false",
1460 driverConfig.batch ? "--batch" : "--no-batch",
1461 "--color", "false"};
1462
1463 // we maintain options in a map so that later occurrences of the same
1464 // option will overwrite the value. To make unit tests work on all platforms,
1465 // we need to make the sequence deterministic and store it in a separate vector
1466 std::vector<std::string> deviceOptionsSequence;
1467 std::unordered_map<std::string, std::string> uniqueDeviceArgs;
1468 auto updateDeviceArguments = [&deviceOptionsSequence, &uniqueDeviceArgs](auto key, auto value) {
1469 if (uniqueDeviceArgs.find(key) == uniqueDeviceArgs.end()) {
1470 // not yet existing, we add the key to the sequence
1471 deviceOptionsSequence.emplace_back(key);
1472 }
1473 uniqueDeviceArgs[key] = value;
1474 };
1475 std::vector<std::string> tmpEnv;
1476 if (defaultStopped) {
1477 tmpArgs.emplace_back("-s");
1478 }
1479
1480 // do the filtering of options:
1481 // 1) forward options belonging to this specific DeviceSpec
1482 // 2) global options defined in getForwardedDeviceOptions and workflow option are
1483 // always forwarded and need to be handled separately
1484 const char* name = spec.name.c_str();
1485 bpo::options_description od; // option descriptions per process
1486 bpo::options_description foDesc; // forwarded options for all processes
1487 ConfigParamsHelper::dpl2BoostOptions(spec.options, od);
1488 od.add_options()(name, bpo::value<std::string>());
1489 ConfigParamsHelper::dpl2BoostOptions(workflowOptions, foDesc);
1490 auto forwardedOptions = getForwardedDeviceOptions();
1492 foDesc.add(forwardedOptions);
1493
1494 // has option --session been specified on the command line?
1495 bool haveSessionArg = false;
1496 using FilterFunctionT = std::function<void(decltype(argc), decltype(argv), decltype(od))>;
1497 bool useDefaultWS = true;
1498
1499 // the filter function will forward command line arguments based on the option
1500 // definition passed to it. All options of the program option definition will be forwarded
1501 // if found in the argument list. If not found they will be added with the default value
1502 FilterFunctionT filterArgsFct = [&](int largc, char** largv, const bpo::options_description& odesc) {
1503 // spec contains options
1504 using namespace bpo::command_line_style;
1505 auto style = (allow_short | short_allow_adjacent | short_allow_next | allow_long | long_allow_adjacent | long_allow_next | allow_sticky | allow_dash_for_short);
1506
1507 bpo::command_line_parser parser{largc, largv};
1508 parser.options(odesc).allow_unregistered();
1509 parser.style(style);
1510 bpo::parsed_options parsed_options = parser.run();
1511
1512 bpo::variables_map varmap;
1513 bpo::store(parsed_options, varmap);
1514 if (varmap.count("environment")) {
1515 auto environment = varmap["environment"].as<std::string>();
1516 split(environment, tmpEnv);
1517 }
1518
1520 if (varmap.count("stacktrace-on-signal") && varmap["stacktrace-on-signal"].as<std::string>() != "none" && varmap["stacktrace-on-signal"].as<std::string>() != "simple") {
1521 char const* preload = getenv("LD_PRELOAD");
1522 if (preload == nullptr || strcmp(preload, "libSegFault.so") == 0) {
1523 tmpEnv.emplace_back("LD_PRELOAD=libSegFault.so");
1524 } else {
1525 tmpEnv.push_back(fmt::format("LD_PRELOAD={}:libSegFault.so", preload));
1526 }
1527 tmpEnv.push_back(fmt::format("SEGFAULT_SIGNALS={}", varmap["stacktrace-on-signal"].as<std::string>()));
1528 }
1529
1530 // options can be grouped per processor spec, the group is entered by
1531 // the option created from the actual processor spec name
1532 // if specified, the following string is interpreted as a sequence
1533 // of arguments
1534 if (varmap.count(name) > 0) {
1535 // strangely enough, the first argument of the group argument string
1536 // is marked as defaulted by the parser and is thus ignored. not fully
1537 // understood but adding a dummy argument in front cures this
1538 auto arguments = "--unused " + varmap[name].as<std::string>();
1539 wordexp_t expansions;
1540 wordexp(arguments.c_str(), &expansions, 0);
1541 bpo::options_description realOdesc = odesc;
1542 realOdesc.add_options()("severity", bpo::value<std::string>());
1543 realOdesc.add_options()("child-driver", bpo::value<std::string>());
1544 realOdesc.add_options()("rate", bpo::value<std::string>());
1545 realOdesc.add_options()("exit-transition-timeout", bpo::value<std::string>());
1546 realOdesc.add_options()("error-on-exit-transition-timeout", bpo::value<bool>()->zero_tokens());
1547 realOdesc.add_options()("data-processing-timeout", bpo::value<std::string>());
1548 realOdesc.add_options()("expected-region-callbacks", bpo::value<std::string>());
1549 realOdesc.add_options()("timeframes-rate-limit", bpo::value<std::string>());
1550 realOdesc.add_options()("environment", bpo::value<std::string>());
1551 realOdesc.add_options()("stacktrace-on-signal", bpo::value<std::string>());
1552 realOdesc.add_options()("post-fork-command", bpo::value<std::string>());
1553 realOdesc.add_options()("bad-alloc-max-attempts", bpo::value<std::string>());
1554 realOdesc.add_options()("bad-alloc-attempt-interval", bpo::value<std::string>());
1555 realOdesc.add_options()("io-threads", bpo::value<std::string>());
1556 realOdesc.add_options()("shm-segment-size", bpo::value<std::string>());
1557 realOdesc.add_options()("shm-mlock-segment", bpo::value<std::string>());
1558 realOdesc.add_options()("shm-mlock-segment-on-creation", bpo::value<std::string>());
1559 realOdesc.add_options()("shm-zero-segment", bpo::value<std::string>());
1560 realOdesc.add_options()("shm-throw-bad-alloc", bpo::value<std::string>());
1561 realOdesc.add_options()("shm-segment-id", bpo::value<std::string>());
1562 realOdesc.add_options()("shm-allocation", bpo::value<std::string>());
1563 realOdesc.add_options()("shm-no-cleanup", bpo::value<std::string>());
1564 realOdesc.add_options()("shmid", bpo::value<std::string>());
1565 realOdesc.add_options()("shm-metadata-msg-size", bpo::value<std::string>()->default_value("0"));
1566 realOdesc.add_options()("shm-monitor", bpo::value<std::string>());
1567 realOdesc.add_options()("channel-prefix", bpo::value<std::string>());
1568 realOdesc.add_options()("network-interface", bpo::value<std::string>());
1569 realOdesc.add_options()("early-forward-policy", bpo::value<std::string>());
1570 realOdesc.add_options()("session", bpo::value<std::string>());
1571 realOdesc.add_options()("signposts", bpo::value<std::string>());
1572 filterArgsFct(expansions.we_wordc, expansions.we_wordv, realOdesc);
1573 wordfree(&expansions);
1574 return;
1575 }
1576
1577 const char* child_driver_key = "child-driver";
1578 if (varmap.count(child_driver_key) > 0) {
1579 auto arguments = varmap[child_driver_key].as<std::string>();
1580 wordexp_t expansions;
1581 wordexp(arguments.c_str(), &expansions, 0);
1582 tmpArgs.insert(tmpArgs.begin(), expansions.we_wordv, expansions.we_wordv + expansions.we_wordc);
1583 }
1584
1585 haveSessionArg = haveSessionArg || varmap.count("session") != 0;
1586 useDefaultWS = useDefaultWS && ((varmap.count("driver-client-backend") == 0) || varmap["driver-client-backend"].as<std::string>() == "ws://");
1587
1588 auto processRawChannelConfig = [&tmpArgs, &spec](const std::string& conf) {
1589 std::stringstream ss(reworkTimeslicePlaceholder(conf, spec));
1590 std::string token;
1591 while (std::getline(ss, token, ';')) { // split to tokens, trim spaces and add each non-empty one with channel-config options
1592 token.erase(token.begin(), std::find_if(token.begin(), token.end(), [](int ch) { return !std::isspace(ch); }));
1593 token.erase(std::find_if(token.rbegin(), token.rend(), [](int ch) { return !std::isspace(ch); }).base(), token.end());
1594 if (!token.empty()) {
1595 tmpArgs.emplace_back("--channel-config");
1596 tmpArgs.emplace_back(token);
1597 }
1598 }
1599 };
1600
1601 // Fast path for an exact, unambiguously declared long name. An option can
1602 // carry more than one long name, so index all of them. A name declared twice
1603 // is mapped to nullptr, so that it falls back to find_nothrow() below and is
1604 // reported as ambiguous, as it would be without this lookup table. Wildcard
1605 // and short-only names simply miss and fall back as well.
1606 std::unordered_map<std::string_view, const bpo::option_description*> odescByName;
1607 odescByName.reserve(odesc.options().size());
1608 for (auto const& optDesc : odesc.options()) {
1609 auto [names, count] = optDesc->long_names();
1610 for (size_t ni = 0; ni < count; ++ni) {
1611 auto [it, inserted] = odescByName.try_emplace(names[ni], optDesc.get());
1612 if (!inserted) {
1613 it->second = nullptr;
1614 }
1615 }
1616 }
1617 for (const auto& varit : varmap) {
1618 // find the option belonging to key, add if the option has been parsed
1619 // and is not defaulted
1620 auto descIt = odescByName.find(varit.first);
1621 const auto* description = (descIt != odescByName.end() && descIt->second != nullptr)
1622 ? descIt->second
1623 : odesc.find_nothrow(varit.first, false);
1624 if (description == nullptr) {
1625 continue;
1626 }
1627
1628 // check the semantics of the value
1629 auto semantic = description->semantic();
1630 const char* optarg = "";
1631 if (!semantic) {
1632 control.options.insert(std::make_pair(varit.first, optarg));
1633 continue;
1634 }
1635
1636 if (semantic->min_tokens() == 0 && varit.second.as<bool>()) {
1637 updateDeviceArguments(fmt::format("--{}", varit.first), "");
1638 control.options.insert(std::make_pair(varit.first, optarg));
1639 continue;
1640 }
1641
1642 // the value semantics allows different properties like
1643 // multitoken, zero_token and composing
1644 // currently only the simple case is supported
1645 assert(semantic->min_tokens() <= 1);
1646 // assert(semantic->max_tokens() && semantic->min_tokens());
1647 if (semantic->min_tokens() == 0) {
1648 control.options.insert(std::make_pair(varit.first, optarg));
1649 continue;
1650 }
1651
1652 if (semantic->min_tokens() > 0) {
1653 std::string stringRep;
1654 if (auto v = boost::any_cast<std::string>(&varit.second.value())) {
1655 stringRep = *v;
1656 } else if (auto v = boost::any_cast<EarlyForwardPolicy>(&varit.second.value())) {
1657 std::stringstream tmp;
1658 tmp << *v;
1659 stringRep = fmt::format("{}", tmp.str());
1660 }
1661 if (varit.first == "channel-config") {
1662 // FIXME: the parameter to channel-config can be a list of configurations separated
1663 // by semicolon. The individual configurations will be separated and added individually.
1664 // The device arguments can then contaoin multiple channel-config entries, but only
1665 // one for the last configuration is added to control.options
1666 processRawChannelConfig(stringRep);
1667 optarg = tmpArgs.back().c_str();
1668 } else {
1669 std::string key(fmt::format("--{}", varit.first));
1670 if (stringRep.length() == 0) {
1671 // in order to identify options without parameter we add a string
1672 // with one blank for the 'blank' parameter, it is filtered out
1673 // further down and a zero-length string is added to argument list
1674 stringRep = " ";
1675 }
1676 updateDeviceArguments(key, stringRep);
1677 optarg = uniqueDeviceArgs[key].c_str();
1678 }
1679 }
1680 control.options.insert(std::make_pair(varit.first, optarg));
1681 }
1682 };
1683
1684 // filter global options and workflow options independent of option groups
1685 filterArgsFct(argc, argv, foDesc);
1686 // filter device options, and handle option groups
1687 filterArgsFct(argc, argv, od);
1688
1689 // Add the channel configuration
1690 for (auto& channel : spec.outputChannels) {
1691 tmpArgs.emplace_back("--channel-config");
1692 tmpArgs.emplace_back(outputChannel2String(channel));
1693 }
1694 for (auto& channel : spec.inputChannels) {
1695 tmpArgs.emplace_back("--channel-config");
1696 tmpArgs.emplace_back(inputChannel2String(channel));
1697 }
1698
1699 // add the session id if not already specified on command line
1700 if (!haveSessionArg) {
1701 updateDeviceArguments(std::string("--session"), "dpl_" + uniqueWorkflowId);
1702 }
1703 // In case we use only ws://, we need to expand the address
1704 // with the correct port.
1705 if (useDefaultWS) {
1706 updateDeviceArguments(std::string("--driver-client-backend"), "ws://0.0.0.0:" + std::to_string(driverPort));
1707 }
1708
1709 if (spec.resourceMonitoringInterval > 0) {
1710 updateDeviceArguments(std::string("--resources-monitoring"), std::to_string(spec.resourceMonitoringInterval));
1711 }
1712
1713 // We create the final option list, depending on the channels
1714 // which are present in a device.
1715 for (auto& arg : tmpArgs) {
1716 execution.args.emplace_back(strdup(arg.c_str()));
1717 }
1718 for (auto& key : deviceOptionsSequence) {
1719 execution.args.emplace_back(strdup(key.c_str()));
1720 std::string const& value = uniqueDeviceArgs[key];
1721 if (value.empty()) {
1722 // this option does not have a parameter
1723 continue;
1724 } else if (value == " ") {
1725 // this was a placeholder for zero-length parameter string in order
1726 // to separate this from options without parameter
1727 execution.args.emplace_back(strdup(""));
1728 } else {
1729 execution.args.emplace_back(strdup(value.c_str()));
1730 }
1731 }
1732 // execvp wants a NULL terminated list.
1733 execution.args.push_back(nullptr);
1734
1735 for (auto& env : tmpEnv) {
1736 execution.environ.emplace_back(strdup(env.c_str()));
1737 }
1738
1739 // FIXME: this should probably be reflected in the GUI
1740 std::ostringstream str;
1741 for (size_t ai = 0; ai < execution.args.size() - 1; ai++) {
1742 if (execution.args[ai] == nullptr) {
1743 LOG(error) << "Bad argument for " << execution.args[ai - 1];
1744 }
1745 assert(execution.args[ai]);
1746 str << " " << execution.args[ai];
1747 }
1748 O2_SIGNPOST_END(device_spec_helpers, poid, "prepareArguments", "The following options are being forwarded to %{public}s: %{public}s",
1749 spec.id.c_str(), str.str().c_str());
1750 }
1751}
1752
1754boost::program_options::options_description DeviceSpecHelpers::getForwardedDeviceOptions()
1755{
1756 // - rate is an option of FairMQ device for ConditionalRun
1757 // - child-driver is not a FairMQ device option but used per device to start to process
1758 bpo::options_description forwardedDeviceOptions;
1759 char const* defaultSignposts = getenv("DPL_SIGNPOSTS") ? getenv("DPL_SIGNPOSTS") : "";
1760 forwardedDeviceOptions.add_options() //
1761 ("severity", bpo::value<std::string>()->default_value("info"), "severity level of the log") //
1762 ("plugin,P", bpo::value<std::string>(), "FairMQ plugin list") //
1763 ("plugin-search-path,S", bpo::value<std::string>(), "FairMQ plugins search path") //
1764 ("control-port", bpo::value<std::string>(), "Utility port to be used by O2 Control") //
1765 ("rate", bpo::value<std::string>(), "rate for a data source device (Hz)") //
1766 ("exit-transition-timeout", bpo::value<std::string>(), "timeout before switching to READY state") //
1767 ("error-on-exit-transition-timeout", bpo::value<bool>()->zero_tokens(), "print error instead of warning when exit transition timer expires") //
1768 ("data-processing-timeout", bpo::value<std::string>(), "timeout after which only calibration can happen") //
1769 ("expected-region-callbacks", bpo::value<std::string>(), "region callbacks to expect before starting") //
1770 ("timeframes-rate-limit", bpo::value<std::string>()->default_value("0"), "how many timeframes can be in flight") //
1771 ("shm-monitor", bpo::value<std::string>(), "whether to use the shared memory monitor") //
1772 ("channel-prefix", bpo::value<std::string>()->default_value(""), "prefix to use for multiplexing multiple workflows in the same session") //
1773 ("bad-alloc-max-attempts", bpo::value<std::string>()->default_value("1"), "throw after n attempts to alloc shm") //
1774 ("bad-alloc-attempt-interval", bpo::value<std::string>()->default_value("50"), "interval between shm alloc attempts in ms") //
1775 ("io-threads", bpo::value<std::string>()->default_value("1"), "number of FMQ io threads") //
1776 ("shm-segment-size", bpo::value<std::string>(), "size of the shared memory segment in bytes") //
1777 ("shm-mlock-segment", bpo::value<std::string>()->default_value("false"), "mlock shared memory segment") //
1778 ("shm-mlock-segment-on-creation", bpo::value<std::string>()->default_value("false"), "mlock shared memory segment once on creation") //
1779 ("shm-zero-segment", bpo::value<std::string>()->default_value("false"), "zero shared memory segment") //
1780 ("shm-throw-bad-alloc", bpo::value<std::string>()->default_value("true"), "throw if insufficient shm memory") //
1781 ("shm-segment-id", bpo::value<std::string>()->default_value("0"), "shm segment id") //
1782 ("shm-allocation", bpo::value<std::string>()->default_value("rbtree_best_fit"), "shm allocation method") //
1783 ("shm-no-cleanup", bpo::value<std::string>()->default_value("false"), "no shm cleanup") //
1784 ("shmid", bpo::value<std::string>(), "shmid") //
1785 ("shm-metadata-msg-size", bpo::value<std::string>()->default_value("0"), "numeric value in B used for padding FairMQ header, see FairMQ v.1.6.0") //
1786 ("environment", bpo::value<std::string>(), "comma separated list of environment variables to set for the device") //
1787 ("stacktrace-on-signal", bpo::value<std::string>()->default_value("simple"), //
1788 "dump stacktrace on specified signal(s) (any of `all`, `segv`, `bus`, `ill`, `abrt`, `fpe`, `sys`.)" //
1789 "Use `simple` to dump only the main thread in a reliable way") //
1790 ("post-fork-command", bpo::value<std::string>(), "post fork command to execute (e.g. numactl {pid}") //
1791 ("session", bpo::value<std::string>(), "unique label for the shared memory session") //
1792 ("network-interface", bpo::value<std::string>(), "network interface to which to bind tpc fmq ports without specified address") //
1793 ("early-forward-policy", bpo::value<EarlyForwardPolicy>()->default_value(EarlyForwardPolicy::NEVER), "when to forward early the messages: never, noraw, always") //
1794 ("configuration,cfg", bpo::value<std::string>(), "configuration connection string") //
1795 ("driver-client-backend", bpo::value<std::string>(), "driver connection string") //
1796 ("monitoring-backend", bpo::value<std::string>(), "monitoring connection string") //
1797 ("dpl-stats-min-online-publishing-interval", bpo::value<std::string>(), "minimum flushing interval for online metrics (in s)") //
1798 ("infologger-mode", bpo::value<std::string>(), "O2_INFOLOGGER_MODE override") //
1799 ("infologger-severity", bpo::value<std::string>(), "minimun FairLogger severity which goes to info logger") //
1800 ("dpl-tracing-flags", bpo::value<std::string>(), "pipe separated list of events to trace") //
1801 ("signposts", bpo::value<std::string>()->default_value(defaultSignposts), //
1802 "comma separated list of signposts to enable (any of `completion`, `data_processor_context`, `stream_context`, `device`, `monitoring_service`)") //
1803 ("log-timestamp-us", bpo::value<bool>()->zero_tokens()->default_value(false), "enable microsecond timestamps in log messages") //
1804 ("child-driver", bpo::value<std::string>(), "external driver to start childs with (e.g. valgrind)"); //
1805
1806 return forwardedDeviceOptions;
1807}
1808
1809bool DeviceSpecHelpers::hasLabel(DeviceSpec const& spec, char const* label)
1810{
1811 auto sameLabel = [other = DataProcessorLabel{{label}}](DataProcessorLabel const& label) { return label == other; };
1812 return std::find_if(spec.labels.begin(), spec.labels.end(), sameLabel) != spec.labels.end();
1813}
1814
1815std::string DeviceSpecHelpers::reworkTimeslicePlaceholder(std::string const& str, DeviceSpec const& spec)
1816{
1817 // find all the possible timeslice variables, extract N and replace
1818 // the variable with the value of spec.inputTimesliceId + N.
1819 std::regex re("\\{timeslice([0-9]+)\\}");
1820 std::smatch match;
1821 std::string fmt = str;
1822 while (std::regex_search(fmt, match, re)) {
1823 auto timeslice = std::stoi(match[1]);
1824 auto replacement = std::to_string(spec.inputTimesliceId + timeslice);
1825 fmt = match.prefix().str() + replacement + match.suffix().str();
1826 }
1827 return fmt;
1828}
1829
1830} // namespace o2::framework
header::DataDescription description
benchmark::State & state
struct uv_timer_s uv_timer_t
struct uv_signal_s uv_signal_t
struct uv_poll_s uv_poll_t
std::vector< OutputRoute > routes
std::ostringstream debug
int32_t i
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
uint16_t pid
Definition RawData.h:2
#define O2_DECLARE_DYNAMIC_LOG(name)
Definition Signpost.h:490
#define O2_SIGNPOST_END(log, id, name, format,...)
Definition Signpost.h:609
#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_SIGNPOST_START(log, id, name, format,...)
Definition Signpost.h:603
StringRef key
virtual void notifyAcceptedOffer(ComputingOffer const &)=0
virtual std::vector< ComputingOffer > getAvailableOffers()=0
bool match(const std::vector< std::string > &queries, const char *pattern)
Definition dcs-ccdb.cxx:229
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
GLint GLsizei count
Definition glcorearb.h:399
GLsizeiptr size
Definition glcorearb.h:659
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLuint start
Definition glcorearb.h:469
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
bpo::variables_map arguments
auto timer_fired(uv_timer_t *timer)
void timer_callback(uv_timer_t *handle)
void signal_callback(uv_signal_t *handle, int)
auto timer_set_period(uv_timer_t *timer)
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
RuntimeErrorRef runtime_error(const char *)
std::vector< OverrideServiceSpec > OverrideServiceSpecs
std::vector< DataProcessorSpec > WorkflowSpec
RuntimeErrorRef runtime_error_f(const char *,...)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::vector< std::string > split(const std::string &str, char delimiter=',')
static char const * typeAsString(enum ChannelType type)
return a ChannelType as a lowercase string
static std::string channelUrl(InputChannelSpec const &)
static char const * methodAsString(enum ChannelMethod method)
return a ChannelMethod as a lowercase string
A computing resource which can be offered to run a device.
static bool dpl2BoostOptions(const std::vector< ConfigParamSpec > &spec, options_description &options, boost::program_options::options_description const &vetos=options_description())
A label that can be associated to a DataProcessorSpec.
static std::optional< ConcreteDataMatcher > asOptionalConcreteDataMatcher(OutputSpec const &spec)
static std::string describe(InputSpec const &spec)
static ConcreteDataTypeMatcher asConcreteDataTypeMatcher(OutputSpec const &spec)
static ConcreteDataMatcher asConcreteDataMatcher(InputSpec const &input)
static void processOutEdgeActions(ConfigContext const &configContext, std::vector< DeviceSpec > &devices, std::vector< DeviceId > &deviceIndex, std::vector< DeviceConnectionId > &connections, ResourceManager &resourceManager, const std::vector< size_t > &outEdgeIndex, const std::vector< DeviceConnectionEdge > &logicalEdges, const std::vector< EdgeAction > &actions, const WorkflowSpec &workflow, const std::vector< OutputSpec > &outputs, std::vector< ChannelConfigurationPolicy > const &channelPolicies, std::vector< SendingPolicy > const &sendingPolicies, std::vector< ForwardingPolicy > const &forwardingPolicies, std::string const &channelPrefix, ComputingOffer const &defaultOffer, OverrideServiceSpecs const &overrideServices={})
static void processInEdgeActions(std::vector< DeviceSpec > &devices, std::vector< DeviceId > &deviceIndex, const std::vector< DeviceConnectionId > &connections, ResourceManager &resourceManager, const std::vector< size_t > &inEdgeIndex, const std::vector< DeviceConnectionEdge > &logicalEdges, const std::vector< EdgeAction > &actions, const WorkflowSpec &workflow, const std::vector< LogicalForwardInfo > &availableForwardsInfo, std::vector< ChannelConfigurationPolicy > const &channelPolicies, std::string const &channelPrefix, ComputingOffer const &defaultOffer, OverrideServiceSpecs const &overrideServices={})
static void validate(WorkflowSpec const &workflow)
static boost::program_options::options_description getForwardedDeviceOptions()
define the options which are forwarded to every child
static std::string inputChannel2String(const InputChannelSpec &channel)
Helper to provide the channel configuration string for an input channel.
static std::string reworkTimeslicePlaceholder(std::string const &str, DeviceSpec const &spec)
static void prepareArguments(bool defaultQuiet, bool defaultStopped, bool intereactive, unsigned short driverPort, DriverConfig const &driverConfig, std::vector< DataProcessorInfo > const &processorInfos, std::vector< DeviceSpec > const &deviceSpecs, std::vector< DeviceExecution > &deviceExecutions, std::vector< DeviceControl > &deviceControls, std::vector< ConfigParamSpec > const &detectedOptions, std::string const &uniqueWorkflowId)
static void reworkShmSegmentSize(std::vector< DataProcessorInfo > &infos)
static void reworkHomogeneousOption(std::vector< DataProcessorInfo > &infos, char const *name, char const *defaultValue)
static void reworkIntegerOption(std::vector< DataProcessorInfo > &infos, char const *name, std::function< long long()> defaultValueCallback, long long startValue, std::function< long long(long long, long long)> bestValue)
static bool hasLabel(DeviceSpec const &spec, char const *label)
static std::string outputChannel2String(const OutputChannelSpec &channel)
Helper to provide the channel configuration string for an output channel.
static void dataProcessorSpecs2DeviceSpecs(const WorkflowSpec &workflow, std::vector< ChannelConfigurationPolicy > const &channelPolicies, std::vector< CompletionPolicy > const &completionPolicies, std::vector< DispatchPolicy > const &dispatchPolicies, std::vector< ResourcePolicy > const &resourcePolicies, std::vector< CallbacksPolicy > const &callbacksPolicies, std::vector< SendingPolicy > const &sendingPolicy, std::vector< ForwardingPolicy > const &forwardingPolicies, std::vector< DeviceSpec > &devices, ResourceManager &resourceManager, std::string const &uniqueWorkflowId, ConfigContext const &configContext, bool optimizeTopology=false, unsigned short resourcesMonitoringInterval=0, std::string const &channelPrefix="", OverrideServiceSpecs const &overrideServices={})
std::vector< DataProcessorLabel > labels
Definition DeviceSpec.h:81
std::string name
The name of the associated DataProcessorSpec.
Definition DeviceSpec.h:50
size_t inputTimesliceId
The time pipelining id of this particular device.
Definition DeviceSpec.h:68
Running state information of a given device.
Definition DeviceState.h:34
bool batch
Whether the driver was started in batch mode or not.
static RouteConfigurator::DanglingConfigurator danglingTimerConfigurator(InputSpec const &matcher)
static RouteConfigurator::DanglingConfigurator danglingConditionConfigurator()
static RouteConfigurator::ExpirationConfigurator expiringConditionConfigurator(InputSpec const &spec, std::string const &sourceChannel)
static RouteConfigurator::ExpirationConfigurator expiringOutOfBandConfigurator(InputSpec const &spec)
static RouteConfigurator::CreationConfigurator enumDrivenConfigurator(InputSpec const &matcher, size_t inputTimeslice, size_t maxInputTimeslices)
static RouteConfigurator::ExpirationConfigurator expiringTimeframeConfigurator()
static RouteConfigurator::CreationConfigurator signalDrivenConfigurator(InputSpec const &matcher, size_t inputTimeslice, size_t maxInputTimeslices)
static RouteConfigurator::ExpirationConfigurator expiringOptionalConfigurator(InputSpec const &spec, std::string const &sourceChannel)
When the record expires, simply create a dummy entry.
static RouteConfigurator::DanglingConfigurator danglingEnumerationConfigurator(InputSpec const &matcher)
static RouteConfigurator::DanglingConfigurator danglingQAConfigurator()
static RouteConfigurator::CreationConfigurator createOptionalConfigurator()
This behaves as data. I.e. we never create it unless data arrives.
static RouteConfigurator::ExpirationConfigurator expiringOOBConfigurator(InputSpec const &spec, std::string const &sourceChannel)
static RouteConfigurator::CreationConfigurator loopEventDrivenConfigurator(InputSpec const &matcher)
static RouteConfigurator::ExpirationConfigurator expiringTimerConfigurator(InputSpec const &spec, std::string const &sourceChannel)
static RouteConfigurator::DanglingConfigurator danglingTimeframeConfigurator()
static RouteConfigurator::ExpirationConfigurator expiringTransientConfigurator(InputSpec const &)
static RouteConfigurator::CreationConfigurator oobDrivenConfigurator()
static RouteConfigurator::CreationConfigurator timeDrivenConfigurator(InputSpec const &matcher)
static RouteConfigurator::DanglingConfigurator danglingOptionalConfigurator(std::vector< InputRoute > const &routes)
This will always exipire an optional record when no data is received.
static RouteConfigurator::DanglingConfigurator danglingOutOfBandConfigurator()
static RouteConfigurator::CreationConfigurator dataDrivenConfigurator()
static RouteConfigurator::ExpirationConfigurator expiringQAConfigurator()
static RouteConfigurator::ExpirationConfigurator expiringEnumerationConfigurator(InputSpec const &spec, std::string const &sourceChannel)
static RouteConfigurator::DanglingConfigurator danglingTransientConfigurator()
static RouteConfigurator::CreationConfigurator fairmqDrivenConfiguration(InputSpec const &spec, int inputTimeslice, int maxInputTimeslices)
std::string binding
A mnemonic name for the input spec.
Definition InputSpec.h:66
std::vector< ConfigParamSpec > metadata
A set of configurables which can be used to customise the InputSpec.
Definition InputSpec.h:76
std::variant< ConcreteDataMatcher, data_matcher::DataDescriptorMatcher > matcher
The actual matcher for the input spec.
Definition InputSpec.h:70
static ExpirationHandler::Handler fetchFromCCDBCache(InputSpec const &spec, std::string const &prefix, std::string const &overrideTimestamp, std::string const &sourceChannel)
static ExpirationHandler::Handler dummy(ConcreteDataMatcher const &spec, std::string const &sourceChannel)
Create a dummy message with the provided ConcreteDataMatcher.
static ExpirationHandler::Handler fetchFromQARegistry()
static ExpirationHandler::Creator enumDrivenCreation(size_t first, size_t last, size_t step, size_t inputTimeslice, size_t maxTimeSliceId, size_t repetitions)
static ExpirationHandler::Handler enumerate(ConcreteDataMatcher const &spec, std::string const &sourceChannel, int64_t orbitOffset, int64_t orbitMultiplier)
Enumerate entries on every invokation.
static ExpirationHandler::Creator timeDrivenCreation(std::vector< std::chrono::microseconds > periods, std::vector< std::chrono::seconds > intervals, std::function< bool(void)> hasTimerFired, std::function< void(uint64_t, uint64_t)> updateTimerPeriod)
static ExpirationHandler::Creator dataDrivenCreation()
Callback which does nothing, waiting for data to arrive.
static ExpirationHandler::Checker expireAlways()
static ExpirationHandler::Handler fetchFromObjectRegistry()
static ExpirationHandler::Checker expectCTP(std::string const &serverUrl, bool waitForCTP)
static ExpirationHandler::Checker expireNever()
static ExpirationHandler::Creator uvDrivenCreation(int loopReason, DeviceState &state)
Callback which creates a new timeslice whenever some libuv event happens.
static ExpirationHandler::Handler fetchFromFairMQ(InputSpec const &spec, std::string const &channelName)
static ExpirationHandler::Handler doNothing()
static ExpirationHandler::Checker expireIfPresent(std::vector< InputRoute > const &schema, ConcreteDataMatcher matcher)
std::function< ExpirationHandler::Creator(DeviceState &, ServiceRegistryRef, ConfigParamRegistry const &)> CreationConfigurator
Definition InputRoute.h:31
std::function< ExpirationHandler::Handler(DeviceState &, ConfigParamRegistry const &)> ExpirationConfigurator
Definition InputRoute.h:33
std::function< ExpirationHandler::Checker(DeviceState &, ConfigParamRegistry const &)> DanglingConfigurator
Definition InputRoute.h:32
static ServiceSpecs filterDisabled(ServiceSpecs originals, OverrideServiceSpecs const &overrides)
static void validateEdges(WorkflowSpec const &workflow, std::vector< DataProcessorPoliciesInfo > const &policiesInfos, std::vector< DeviceConnectionEdge > const &edges, std::vector< OutputSpec > const &outputs)
static void constructGraph(const WorkflowSpec &workflow, std::vector< DeviceConnectionEdge > &logicalEdges, std::vector< OutputSpec > &outputs, std::vector< LogicalForwardInfo > &availableForwardsInfo)
static std::vector< EdgeAction > computeOutEdgeActions(const std::vector< DeviceConnectionEdge > &edges, const std::vector< size_t > &index)
static void sortEdges(std::vector< size_t > &inEdgeIndex, std::vector< size_t > &outEdgeIndex, const std::vector< DeviceConnectionEdge > &edges)
static std::vector< EdgeAction > computeInEdgeActions(const std::vector< DeviceConnectionEdge > &edges, const std::vector< size_t > &index)
VectorOfTObjectPtrs other
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< ChannelData > channels
const std::string str