34#if defined(__APPLE__) || defined(NDEBUG)
35#define O2_SIGNPOST_IMPLEMENTATION
60#include <fairmq/Parts.h>
61#include <fairmq/Socket.h>
62#include <fairmq/ProgOptions.h>
63#include <fairmq/shmem/Message.h>
64#include <Configuration/ConfigurationInterface.h>
65#include <Configuration/ConfigurationFactory.h>
66#include <Monitoring/Monitoring.h>
68#include <TClonesArray.h>
70#include <fmt/ostream.h>
78#include <boost/property_tree/json_parser.hpp>
136 return devices[running.
index];
146 : mRunningDevice{running},
147 mConfigRegistry{nullptr},
148 mServiceRegistry{registry}
150 GetConfig()->Subscribe<std::string>(
"dpl", [®istry = mServiceRegistry](
const std::string&
key, std::string
value) {
151 if (
key ==
"cleanup") {
155 int64_t newCleanupCount = std::stoll(
value);
156 if (newCleanupCount <= cleanupCount) {
159 deviceState.cleanupCount.store(newCleanupCount);
160 for (
auto& info : deviceState.inputChannelInfos) {
161 fair::mq::Parts parts;
162 while (info.channel->Receive(parts, 0)) {
163 LOGP(
debug,
"Dropping {} parts", parts.Size());
164 if (parts.Size() == 0) {
172 std::function<
void(
const fair::mq::State)> stateWatcher = [
this, ®istry = mServiceRegistry](
const fair::mq::State
state) ->
void {
177 control.notifyDeviceState(fair::mq::GetStateName(
state));
180 if (deviceState.nextFairMQState.empty() ==
false) {
181 auto state = deviceState.nextFairMQState.back();
183 deviceState.nextFairMQState.pop_back();
188 this->SubscribeToStateChange(
"99-dpl", stateWatcher);
190 auto* poolSizeEnv = getenv(
"DPL_THREADPOOL_SIZE");
194 size_t numStreams = poolSizeEnv ? std::max(0, std::atoi(poolSizeEnv)) : 0;
195 mStreams.resize(std::max(numStreams, 1UL));
196 mHandles.resize(std::max(numStreams, 1UL));
204 mAwakeHandle->data = &
state;
206 LOG(
error) <<
"Unable to initialise subscription";
210 SubscribeToNewTransition(
"dpl", [wakeHandle = mAwakeHandle](fair::mq::Transition
t) {
211 int res = uv_async_send(wakeHandle);
213 LOG(
error) <<
"Unable to notify subscription";
215 LOG(
debug) <<
"State transition requested";
243 using o2::monitoring::Metric;
244 using o2::monitoring::Monitoring;
245 using o2::monitoring::tags::Key;
246 using o2::monitoring::tags::Value;
250 stats.totalConsumedBytes += accumulatedConsumed.
sharedMemory;
253 stats.totalConsumedTimeslices += std::min<int64_t>(accumulatedConsumed.
timeslices, 1);
257 dpStats.processCommandQueue();
267 dpStats.processCommandQueue();
270 for (
auto& consumer :
state.offerConsumers) {
271 quotaEvaluator.consume(task->id.index, consumer, reportConsumedOffer);
273 state.offerConsumers.clear();
274 quotaEvaluator.handleExpired(reportExpiredOffer);
275 quotaEvaluator.dispose(task->id.index);
276 task->running =
false;
304 O2_SIGNPOST_EVENT_EMIT(sockets, sid,
"socket_state",
"Data pending on socket for channel %{public}s", context->name);
308 O2_SIGNPOST_END(sockets, sid,
"socket_state",
"Socket connected for channel %{public}s", context->name);
310 O2_SIGNPOST_START(sockets, sid,
"socket_state",
"Socket connected for read in context %{public}s", context->name);
311 uv_poll_start(poller, UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED, &
on_socket_polled);
314 O2_SIGNPOST_START(sockets, sid,
"socket_state",
"Socket connected for write for channel %{public}s", context->name);
322 case UV_DISCONNECT: {
323 O2_SIGNPOST_END(sockets, sid,
"socket_state",
"Socket disconnected in context %{public}s", context->name);
325 case UV_PRIORITIZED: {
326 O2_SIGNPOST_EVENT_EMIT(sockets, sid,
"socket_state",
"Socket prioritized for context %{public}s", context->name);
338 LOGP(fatal,
"Error while polling {}: {}", context->name, status);
343 O2_SIGNPOST_EVENT_EMIT(sockets, sid,
"socket_state",
"Data pending on socket for channel %{public}s", context->name);
345 assert(context->channelInfo);
346 context->channelInfo->readPolled =
true;
349 O2_SIGNPOST_END(sockets, sid,
"socket_state",
"OOB socket connected for channel %{public}s", context->name);
351 O2_SIGNPOST_START(sockets, sid,
"socket_state",
"OOB socket connected for read in context %{public}s", context->name);
354 O2_SIGNPOST_START(sockets, sid,
"socket_state",
"OOB socket connected for write for channel %{public}s", context->name);
358 case UV_DISCONNECT: {
359 O2_SIGNPOST_END(sockets, sid,
"socket_state",
"OOB socket disconnected in context %{public}s", context->name);
362 case UV_PRIORITIZED: {
363 O2_SIGNPOST_EVENT_EMIT(sockets, sid,
"socket_state",
"OOB socket prioritized for context %{public}s", context->name);
384 context.statelessProcess = spec.algorithm.onProcess;
386 context.error = spec.algorithm.onError;
387 context.
initError = spec.algorithm.onInitError;
390 if (configStore ==
nullptr) {
391 std::vector<std::unique_ptr<ParamRetriever>> retrievers;
392 retrievers.emplace_back(std::make_unique<FairOptionsRetriever>(GetConfig()));
393 configStore = std::make_unique<ConfigParamStore>(spec.options, std::move(retrievers));
394 configStore->preload();
395 configStore->activate();
398 using boost::property_tree::ptree;
401 for (
auto&
entry : configStore->store()) {
402 std::stringstream ss;
404 if (
entry.second.empty() ==
false) {
405 boost::property_tree::json_parser::write_json(ss,
entry.second,
false);
408 str =
entry.second.get_value<std::string>();
410 std::string configString = fmt::format(
"[CONFIG] {}={} 1 {}",
entry.first,
str, configStore->provenance(
entry.first.c_str())).c_str();
414 mConfigRegistry = std::make_unique<ConfigParamRegistry>(std::move(configStore));
417 if (context.initError) {
418 context.initErrorHandling = [&errorCallback = context.initError,
431 errorCallback(errorContext);
434 context.initErrorHandling = [&serviceRegistry = mServiceRegistry](
RuntimeErrorRef e) {
449 context.expirationHandlers.clear();
450 context.init = spec.algorithm.onInit;
452 static bool noCatch = getenv(
"O2_NO_CATCHALL_EXCEPTIONS") && strcmp(getenv(
"O2_NO_CATCHALL_EXCEPTIONS"),
"0");
453 InitContext initContext{*mConfigRegistry, mServiceRegistry};
457 context.statefulProcess = context.init(initContext);
459 if (context.initErrorHandling) {
460 (context.initErrorHandling)(
e);
465 context.statefulProcess = context.init(initContext);
466 }
catch (std::exception& ex) {
471 (context.initErrorHandling)(
e);
473 (context.initErrorHandling)(
e);
478 state.inputChannelInfos.resize(spec.inputChannels.size());
482 int validChannelId = 0;
483 for (
size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
484 auto&
name = spec.inputChannels[ci].name;
485 if (
name.find(spec.channelPrefix +
"from_internal-dpl-clock") == 0) {
490 state.inputChannelInfos[ci].id = {validChannelId++};
495 if (spec.callbacksPolicy.policy !=
nullptr) {
496 InitContext initContext{*mConfigRegistry, mServiceRegistry};
501 auto* options = GetConfig();
502 for (
size_t si = 0; si < mStreams.size(); ++si) {
525 while (ri != quotaEvaluator.mOffers.size()) {
526 auto& offer = quotaEvaluator.mOffers[ri];
532 if (offer.valid && offer.sharedMemory != 0) {
539 for (
auto& offer : quotaEvaluator.mOffers) {
540 if (offer.valid ==
false) {
543 offer.sharedMemory = 1000000000;
564 if (oldestTimeslice.timeslice.value <= decongestion.lastTimeslice) {
565 LOG(
debug) <<
"Not sending already sent oldest possible timeslice " << oldestTimeslice.timeslice.value;
568 for (
int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
569 auto& info = proxy.getForwardChannelInfo(
ChannelIndex{fi});
574 O2_SIGNPOST_EVENT_EMIT(async_queue, aid,
"forwardInputsCallback",
"Skipping channel %{public}s because it's not a DPL channel",
580 O2_SIGNPOST_EVENT_EMIT(async_queue, aid,
"forwardInputsCallback",
"Forwarding to channel %{public}s oldest possible timeslice %zu, prio 20",
581 info.name.c_str(), oldestTimeslice.timeslice.value);
595 O2_SIGNPOST_START(forwarding, sid,
"forwardInputs",
"Starting forwarding for slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s",
596 slot.index, oldestTimeslice.timeslice.value, copy ?
"with copy" :
"", copy && consume ?
" and " :
"", consume ?
"with consume" :
"");
599 for (
int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
600 if (forwardedParts[fi].
Size() == 0) {
604 auto& parts = forwardedParts[fi];
605 if (
info.policy ==
nullptr) {
616 O2_SIGNPOST_EVENT_EMIT(async_queue, aid,
"forwardInputs",
"Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value);
627 O2_SIGNPOST_START(forwarding, sid,
"forwardInputs",
"Cleaning up slot %zu with oldestTimeslice %zu %{public}s%{public}s%{public}s",
628 slot.index, oldestTimeslice.timeslice.value, copy ?
"with copy" :
"", copy && consume ?
" and " :
"", consume ?
"with consume" :
"");
631 for (
size_t ii = 0, ie = currentSetOfInputs.size(); ii < ie; ++ii) {
644 if (infos.empty() ==
false) {
645 std::vector<fair::mq::RegionInfo> toBeNotified;
646 toBeNotified.swap(infos);
647 static bool dummyRead = getenv(
"DPL_DEBUG_MAP_ALL_SHM_REGIONS") && atoi(getenv(
"DPL_DEBUG_MAP_ALL_SHM_REGIONS"));
648 for (
auto const& info : toBeNotified) {
668void DataProcessingDevice::initPollers()
676 if ((context.statefulProcess !=
nullptr) || (context.statelessProcess !=
nullptr)) {
677 for (
auto& [channelName, channel] : GetChannels()) {
679 for (
size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
680 auto& channelSpec = spec.inputChannels[ci];
681 channelInfo = &
state.inputChannelInfos[ci];
682 if (channelSpec.name != channelName) {
685 channelInfo->
channel = &this->GetChannel(channelName, 0);
688 if ((
channelName.rfind(
"from_internal-dpl", 0) == 0) &&
689 (
channelName.rfind(
"from_internal-dpl-aod", 0) != 0) &&
690 (
channelName.rfind(
"from_internal-dpl-ccdb-backend", 0) != 0) &&
691 (
channelName.rfind(
"from_internal-dpl-injected", 0)) != 0) {
692 LOGP(detail,
"{} is an internal channel. Skipping as no input will come from there.", channelName);
696 if (
channelName.rfind(
"from_" + spec.name +
"_", 0) == 0) {
697 LOGP(detail,
"{} is to send data. Not polling.", channelName);
702 LOGP(detail,
"{} is not a DPL socket. Not polling.", channelName);
708 size_t zmq_fd_len =
sizeof(zmq_fd);
711 channel[0].GetSocket().GetOption(
"fd", &zmq_fd, &zmq_fd_len);
716 LOGP(detail,
"Polling socket for {}", channelName);
719 pCtx->loop =
state.loop;
721 pCtx->state = &
state;
723 assert(channelInfo !=
nullptr);
724 pCtx->channelInfo = channelInfo;
725 pCtx->socket = &channel[0].GetSocket();
728 uv_poll_init(
state.loop, poller, zmq_fd);
730 LOGP(detail,
"{} is an out of band channel.", channelName);
731 state.activeOutOfBandPollers.push_back(poller);
734 state.activeInputPollers.push_back(poller);
740 if (
state.activeInputPollers.empty() &&
741 state.activeOutOfBandPollers.empty() &&
742 state.activeTimers.empty() &&
743 state.activeSignals.empty()) {
747 if (
state.inputChannelInfos.empty()) {
748 LOGP(detail,
"No input channels. Setting exit transition timeout to 0.");
749 deviceContext.exitTransitionTimeout = 0;
751 for (
auto& [channelName, channel] : GetChannels()) {
752 if (
channelName.rfind(spec.channelPrefix +
"from_internal-dpl", 0) == 0) {
753 LOGP(detail,
"{} is an internal channel. Not polling.", channelName);
756 if (
channelName.rfind(spec.channelPrefix +
"from_" + spec.name +
"_", 0) == 0) {
757 LOGP(detail,
"{} is an out of band channel. Not polling for output.", channelName);
762 size_t zmq_fd_len =
sizeof(zmq_fd);
765 channel[0].GetSocket().GetOption(
"fd", &zmq_fd, &zmq_fd_len);
767 LOGP(
error,
"Cannot get file descriptor for channel {}", channelName);
770 LOG(detail) <<
"Polling socket for " << channel[0].GetName();
774 pCtx->loop =
state.loop;
776 pCtx->state = &
state;
780 uv_poll_init(
state.loop, poller, zmq_fd);
781 state.activeOutputPollers.push_back(poller);
785 LOGP(detail,
"This is a fake device so we exit after the first iteration.");
786 deviceContext.exitTransitionTimeout = 0;
792 uv_timer_init(
state.loop, timer);
793 timer->data = &
state;
794 uv_update_time(
state.loop);
796 state.activeTimers.push_back(timer);
800void DataProcessingDevice::startPollers()
806 for (
auto* poller :
state.activeInputPollers) {
812 for (
auto& poller :
state.activeOutOfBandPollers) {
816 for (
auto* poller :
state.activeOutputPollers) {
825 uv_timer_init(
state.loop, deviceContext.gracePeriodTimer);
828 deviceContext.dataProcessingGracePeriodTimer->data =
new ServiceRegistryRef(mServiceRegistry);
829 uv_timer_init(
state.loop, deviceContext.dataProcessingGracePeriodTimer);
832void DataProcessingDevice::stopPollers()
837 LOGP(detail,
"Stopping {} input pollers",
state.activeInputPollers.size());
838 for (
auto* poller :
state.activeInputPollers) {
841 uv_poll_stop(poller);
844 LOGP(detail,
"Stopping {} out of band pollers",
state.activeOutOfBandPollers.size());
845 for (
auto* poller :
state.activeOutOfBandPollers) {
846 uv_poll_stop(poller);
849 LOGP(detail,
"Stopping {} output pollers",
state.activeOutOfBandPollers.size());
850 for (
auto* poller :
state.activeOutputPollers) {
853 uv_poll_stop(poller);
857 uv_timer_stop(deviceContext.gracePeriodTimer);
859 free(deviceContext.gracePeriodTimer);
860 deviceContext.gracePeriodTimer =
nullptr;
862 uv_timer_stop(deviceContext.dataProcessingGracePeriodTimer);
864 free(deviceContext.dataProcessingGracePeriodTimer);
865 deviceContext.dataProcessingGracePeriodTimer =
nullptr;
880 for (
auto&
di : distinct) {
881 auto& route = spec.inputs[
di];
882 if (route.configurator.has_value() ==
false) {
887 .
name = route.configurator->name,
889 .lifetime = route.matcher.lifetime,
890 .creator = route.configurator->creatorConfigurator(
state, mServiceRegistry, *mConfigRegistry),
891 .checker = route.configurator->danglingConfigurator(
state, *mConfigRegistry),
892 .handler = route.configurator->expirationConfigurator(
state, *mConfigRegistry)};
893 context.expirationHandlers.emplace_back(std::move(handler));
896 if (
state.awakeMainThread ==
nullptr) {
902 deviceContext.expectedRegionCallbacks = std::stoi(fConfig->GetValue<std::string>(
"expected-region-callbacks"));
903 deviceContext.exitTransitionTimeout = std::stoi(fConfig->GetValue<std::string>(
"exit-transition-timeout"));
904 deviceContext.dataProcessingTimeout = std::stoi(fConfig->GetValue<std::string>(
"data-processing-timeout"));
906 for (
auto& channel : GetChannels()) {
907 channel.second.at(0).Transport()->SubscribeToRegionEvents([&context = deviceContext,
908 ®istry = mServiceRegistry,
909 &pendingRegionInfos = mPendingRegionInfos,
910 ®ionInfoMutex = mRegionInfoMutex](fair::mq::RegionInfo info) {
911 std::lock_guard<std::mutex> lock(regionInfoMutex);
912 LOG(detail) <<
">>> Region info event" << info.event;
913 LOG(detail) <<
"id: " << info.id;
914 LOG(detail) <<
"ptr: " << info.ptr;
915 LOG(detail) <<
"size: " << info.size;
916 LOG(detail) <<
"flags: " << info.flags;
919 pendingRegionInfos.push_back(info);
932 if (deviceContext.sigusr1Handle ==
nullptr) {
934 deviceContext.sigusr1Handle->data = &mServiceRegistry;
935 uv_signal_init(
state.loop, deviceContext.sigusr1Handle);
943 deviceContext.sigusr1Handle->data = &mServiceRegistry;
946 DataProcessingDevice::initPollers();
954 LOG(
error) <<
"DataProcessor " <<
state.lastActiveDataProcessor.load()->spec->name <<
" was unexpectedly active";
966 O2_SIGNPOST_END(
device, cid,
"InitTask",
"Exiting InitTask callback waiting for the remaining region callbacks.");
968 auto hasPendingEvents = [&mutex = mRegionInfoMutex, &pendingRegionInfos = mPendingRegionInfos](
DeviceContext& deviceContext) {
969 std::lock_guard<std::mutex> lock(mutex);
970 return (pendingRegionInfos.empty() ==
false) || deviceContext.expectedRegionCallbacks > 0;
977 while (hasPendingEvents(deviceContext)) {
979 uv_run(
state.loop, UV_RUN_ONCE);
983 std::lock_guard<std::mutex> lock(mRegionInfoMutex);
994 bool enableRateLimiting = std::stoi(fConfig->GetValue<std::string>(
"timeframes-rate-limit"));
1003 if (enableRateLimiting ==
false && spec.name.find(
"internal-dpl-injected-dummy-sink") != std::string::npos) {
1006 if (enableRateLimiting) {
1007 for (
auto& spec : spec.outputs) {
1008 if (spec.matcher.binding.value ==
"dpl-summary") {
1015 context.
registry = &mServiceRegistry;
1018 if (context.
error !=
nullptr) {
1032 errorCallback(errorContext);
1046 switch (deviceContext.processingPolicies.
error) {
1057 auto decideEarlyForward = [&context, &deviceContext, &spec,
this]() ->
ForwardPolicy {
1065 for (
auto& forward : spec.forwards) {
1077 for (
auto&
label : spec.labels) {
1078 if (
label.value ==
"output-proxy") {
1087 if (spec.forwards.empty() ==
false) {
1093 forwardPolicy = defaultEarlyForwardPolicy;
1096 forwardPolicy = defaultEarlyForwardPolicy;
1100 bool onlyConditions =
true;
1101 bool overriddenEarlyForward =
false;
1102 for (
auto& forwarded : spec.forwards) {
1103 if (forwarded.matcher.lifetime != Lifetime::Condition) {
1104 onlyConditions =
false;
1108 overriddenEarlyForward =
true;
1112 if (forwarded.matcher.lifetime == Lifetime::Optional) {
1114 overriddenEarlyForward =
true;
1119 if (!overriddenEarlyForward && onlyConditions) {
1120 forwardPolicy = defaultEarlyForwardPolicy;
1121 LOG(detail) <<
"Enabling early forwarding because only conditions to be forwarded";
1123 return forwardPolicy;
1135 state.quitRequested =
false;
1138 for (
auto& info :
state.inputChannelInfos) {
1150 for (
size_t i = 0;
i < mStreams.size(); ++
i) {
1153 context.preStartStreamCallbacks(streamRef);
1155 }
catch (std::exception&
e) {
1173 using o2::monitoring::Metric;
1174 using o2::monitoring::Monitoring;
1175 using o2::monitoring::tags::Key;
1176 using o2::monitoring::tags::Value;
1179 monitoring.send(
Metric{(
uint64_t)1,
"device_state"}.addTag(Key::Subsystem, Value::DPL));
1187 using o2::monitoring::Metric;
1188 using o2::monitoring::Monitoring;
1189 using o2::monitoring::tags::Key;
1190 using o2::monitoring::tags::Value;
1193 monitoring.send(
Metric{(
uint64_t)0,
"device_state"}.addTag(Key::Subsystem, Value::DPL));
1212 bool firstLoop =
true;
1216 auto* poolSizeEnv = getenv(
"DPL_THREADPOOL_SIZE");
1217 bool dplEnableMultithreding = poolSizeEnv && std::atoi(poolSizeEnv) > 0;
1220 if (
state.nextFairMQState.empty() ==
false) {
1221 (
void)this->ChangeState(
state.nextFairMQState.back());
1222 state.nextFairMQState.pop_back();
1227 std::lock_guard<std::mutex> lock(mRegionInfoMutex);
1240 state.lastActiveDataProcessor.compare_exchange_strong(lastActive,
nullptr);
1242 auto shouldNotWait = (lastActive !=
nullptr &&
1246 shouldNotWait =
true;
1249 if (lastActive !=
nullptr) {
1252 if (NewStatePending()) {
1254 shouldNotWait =
true;
1260 O2_SIGNPOST_EVENT_EMIT(
device, lid,
"run_loop",
"State transition requested and we are now in Idle. We can consider it to be completed.");
1263 if (
state.severityStack.empty() ==
false) {
1264 fair::Logger::SetConsoleSeverity((fair::Severity)
state.severityStack.back());
1265 state.severityStack.pop_back();
1271 state.firedTimers.clear();
1273 state.severityStack.push_back((
int)fair::Logger::GetConsoleSeverity());
1274 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
1289 forwardInputs(registry, slot, dropped, oldestOutputInfo,
false,
true);
1294 auto oldestPossibleTimeslice = relayer.getOldestPossibleOutput();
1296 if (shouldNotWait ==
false) {
1300 O2_SIGNPOST_END(
device, lid,
"run_loop",
"Run loop completed. %{}s", shouldNotWait ?
"Will immediately schedule a new one" :
"Waiting for next event.");
1301 uv_run(
state.loop, shouldNotWait ? UV_RUN_NOWAIT : UV_RUN_ONCE);
1303 if ((
state.loopReason &
state.tracingFlags) != 0) {
1304 state.severityStack.push_back((
int)fair::Logger::GetConsoleSeverity());
1305 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
1306 }
else if (
state.severityStack.empty() ==
false) {
1307 fair::Logger::SetConsoleSeverity((fair::Severity)
state.severityStack.back());
1308 state.severityStack.pop_back();
1317 if (!
state.pendingOffers.empty()) {
1330 std::lock_guard<std::mutex> lock(mRegionInfoMutex);
1338 assert(mStreams.size() == mHandles.size());
1341 for (
size_t ti = 0; ti < mStreams.size(); ti++) {
1342 auto& taskInfo = mStreams[ti];
1343 if (taskInfo.running) {
1347 streamRef.index = ti;
1349 using o2::monitoring::Metric;
1350 using o2::monitoring::Monitoring;
1351 using o2::monitoring::tags::Key;
1352 using o2::monitoring::tags::Value;
1355 if (streamRef.index != -1) {
1358 uv_work_t&
handle = mHandles[streamRef.index];
1360 handle.data = &mStreams[streamRef.index];
1368 dpStats.processCommandQueue();
1379 struct SchedulingStats {
1385 enum :
size_t { minWarnDelay = 50,
1387 minHealthToForget = 60000 };
1388 std::atomic<size_t> lastScheduled = 0;
1389 std::atomic<size_t> numberOfUnscheduledSinceLastScheduled = 0;
1390 std::atomic<size_t> numberOfUnscheduled = 0;
1391 std::atomic<size_t> numberOfScheduled = 0;
1394 std::atomic<size_t> warnAboveDelay = minWarnDelay;
1397 std::atomic<size_t> healthySince = 0;
1404 void decayWarnThreshold(
size_t now)
1406 size_t const since = healthySince.exchange(0);
1410 size_t healthy = now - since;
1411 if (healthy < minHealthToForget) {
1414 size_t threshold = warnAboveDelay;
1415 while (threshold > minWarnDelay && healthy >= healthToForget * threshold) {
1416 healthy -= healthToForget * threshold;
1417 threshold = std::max<size_t>(threshold / 2, minWarnDelay);
1419 warnAboveDelay = threshold;
1422 static SchedulingStats schedulingStats;
1427 stream.registry = &mServiceRegistry;
1428 schedulingStats.lastScheduled = uv_now(
state.loop);
1429 schedulingStats.numberOfScheduled++;
1430 if (schedulingStats.numberOfUnscheduledSinceLastScheduled != 0) {
1431 schedulingStats.healthySince = schedulingStats.lastScheduled.load();
1433 schedulingStats.numberOfUnscheduledSinceLastScheduled = 0;
1434 O2_SIGNPOST_EVENT_EMIT(scheduling, sid,
"Run",
"Enough resources to schedule computation on stream %d", streamRef.index);
1435 if (dplEnableMultithreding) [[unlikely]] {
1443 auto const lastSched = schedulingStats.lastScheduled.load();
1444 auto const schedInfo = lastSched ? fmt::format(
", last scheduled {} ms ago", uv_now(
state.loop) - lastSched) : std::string(
", never successfully scheduled");
1445 auto const buildMissingInfo = [&]() {
1446 auto const& required = spec.resourcePolicy.minRequired;
1447 std::string missingInfo;
1448 if (required.sharedMemory > 0 && accumulated.sharedMemory < required.sharedMemory) {
1449 missingInfo += fmt::format(
" shared memory (have {} MB, need {} MB)", accumulated.sharedMemory / 1000000, required.sharedMemory / 1000000);
1451 if (required.timeslices > 0 && accumulated.timeslices < required.timeslices) {
1452 missingInfo += fmt::format(
" timeslices (have {}, need {})", accumulated.timeslices, required.timeslices);
1454 if (required.cpu > 0 && accumulated.cpu < required.cpu) {
1455 missingInfo += fmt::format(
" CPU cores (have {}, need {})", accumulated.cpu, required.cpu);
1457 if (required.memory > 0 && accumulated.memory < required.memory) {
1458 missingInfo += fmt::format(
" memory (have {} MB, need {} MB)", accumulated.memory / 1000000, required.memory / 1000000);
1460 return missingInfo.empty() ? std::string(
" (policy: ") + spec.resourcePolicy.name +
")" :
" -" + missingInfo;
1462 size_t const now = uv_now(
state.loop);
1463 schedulingStats.decayWarnThreshold(now);
1464 size_t const timeSinceLastScheduled = lastSched ? now - lastSched : 0;
1467 bool const warn = schedulingStats.numberOfUnscheduledSinceLastScheduled != 0 &&
1468 timeSinceLastScheduled >= schedulingStats.warnAboveDelay;
1469 auto const missingStr = buildMissingInfo();
1471 schedulingStats.warnAboveDelay = timeSinceLastScheduled * 2;
1473 "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.",
1475 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1477 missingStr.c_str());
1480 "Not enough resources to schedule computation on stream %d. %zu consecutive skips%s. Missing:%s. Data is not lost and it will be scheduled again.",
1482 schedulingStats.numberOfUnscheduledSinceLastScheduled.load(),
1484 missingStr.c_str());
1486 schedulingStats.numberOfUnscheduled++;
1487 schedulingStats.numberOfUnscheduledSinceLastScheduled++;
1494 O2_SIGNPOST_END(
device, lid,
"run_loop",
"Run loop completed. Transition handling state %d.", (
int)
state.transitionHandling);
1497 for (
size_t ci = 0; ci < spec.inputChannels.size(); ++ci) {
1498 auto& info =
state.inputChannelInfos[ci];
1499 info.parts.fParts.clear();
1528 context.allDone = std::any_of(
state.inputChannelInfos.begin(),
state.inputChannelInfos.end(), [cid](
const auto& info) {
1530 O2_SIGNPOST_EVENT_EMIT(device, cid,
"do_prepare",
"Input channel %{public}s%{public}s has %zu parts left and is in state %d.",
1531 info.channel->GetName().c_str(), (info.id.value == ChannelIndex::INVALID ?
" (non DPL)" :
""), info.parts.fParts.size(), (int)info.state);
1533 O2_SIGNPOST_EVENT_EMIT(device, cid,
"do_prepare",
"External channel %d is in state %d.", info.id.value, (int)info.state);
1541 static std::vector<int> pollOrder;
1542 pollOrder.resize(
state.inputChannelInfos.size());
1543 std::iota(pollOrder.begin(), pollOrder.end(), 0);
1544 std::sort(pollOrder.begin(), pollOrder.end(), [&infos =
state.inputChannelInfos](
int a,
int b) {
1545 return infos[a].oldestForChannel.value < infos[b].oldestForChannel.value;
1549 if (pollOrder.empty()) {
1553 auto currentOldest =
state.inputChannelInfos[pollOrder.front()].oldestForChannel;
1554 auto currentNewest =
state.inputChannelInfos[pollOrder.back()].oldestForChannel;
1555 auto delta = currentNewest.value - currentOldest.value;
1557 (int64_t)currentOldest.value, (int64_t)currentNewest.value, (int64_t)delta);
1558 auto& infos =
state.inputChannelInfos;
1560 if (context.balancingInputs) {
1562 static uint64_t ahead = getenv(
"DPL_MAX_CHANNEL_AHEAD") ? std::atoll(getenv(
"DPL_MAX_CHANNEL_AHEAD")) :
std::
max(8,
std::
min(pipelineLength - 48, pipelineLength / 2));
1563 auto newEnd = std::remove_if(pollOrder.begin(), pollOrder.end(), [&infos, limitNew = currentOldest.value + ahead](
int a) ->
bool {
1564 return infos[a].oldestForChannel.value > limitNew;
1566 for (
auto it = pollOrder.begin(); it < pollOrder.end(); it++) {
1567 const auto& channelInfo =
state.inputChannelInfos[*it];
1573 bool shouldBeRunning = it < newEnd;
1574 if (running != shouldBeRunning) {
1575 uv_poll_start(poller, shouldBeRunning ? UV_READABLE | UV_DISCONNECT | UV_PRIORITIZED : 0, &
on_socket_polled);
1581 pollOrder.erase(newEnd, pollOrder.end());
1583 O2_SIGNPOST_END(
device, dpid,
"do_prepare",
"%zu channels pass the channel inbalance balance check.", pollOrder.size());
1585 for (
auto sci : pollOrder) {
1586 auto&
info =
state.inputChannelInfos[sci];
1591 context.allDone =
false;
1596 if (
info.parts.Size()) {
1599 O2_SIGNPOST_END(
device, cid,
"channels",
"Flushing channel %s which is in state %d and has %zu parts still pending.",
1600 info.channel->GetName().c_str(), (
int)
info.state,
info.parts.Size());
1603 if (
info.channel ==
nullptr) {
1604 O2_SIGNPOST_END(
device, cid,
"channels",
"Channel %s which is in state %d is nullptr and has %zu parts still pending.",
1605 info.channel->GetName().c_str(), (
int)
info.state,
info.parts.Size());
1610 O2_SIGNPOST_END(
device, cid,
"channels",
"Channel %s which is in state %d is not a DPL channel and has %zu parts still pending.",
1611 info.channel->GetName().c_str(), (
int)
info.state,
info.parts.Size());
1614 auto& socket =
info.channel->GetSocket();
1619 if (
info.hasPendingEvents == 0) {
1620 socket.Events(&
info.hasPendingEvents);
1622 if ((
info.hasPendingEvents & 1) == 0 && (
info.parts.Size() == 0)) {
1623 O2_SIGNPOST_END(
device, cid,
"channels",
"No pending events and no remaining parts to process for channel %{public}s",
info.channel->GetName().c_str());
1629 info.readPolled =
false;
1638 bool newMessages =
false;
1640 O2_SIGNPOST_EVENT_EMIT(
device, cid,
"channels",
"Receiving loop called for channel %{public}s (%d) with oldest possible timeslice %zu",
1641 info.channel->GetName().c_str(),
info.id.value,
info.oldestForChannel.value);
1642 if (
info.parts.Size() < 64) {
1643 fair::mq::Parts parts;
1644 info.channel->Receive(parts, 0);
1648 for (
auto&& part : parts) {
1649 info.parts.fParts.emplace_back(std::move(part));
1651 newMessages |=
true;
1654 if (
info.parts.Size() >= 0) {
1666 socket.Events(&
info.hasPendingEvents);
1667 if (
info.hasPendingEvents) {
1668 info.readPolled =
false;
1671 state.lastActiveDataProcessor.store(&context);
1675 info.channel->GetName().c_str(),
info.id.value);
1691 streamContext.completed.clear();
1692 streamContext.completed.reserve(16);
1694 state.lastActiveDataProcessor.store(&context);
1698 context.preDanglingCallbacks(danglingContext);
1699 if (
state.lastActiveDataProcessor.load() ==
nullptr) {
1702 auto activity =
ref.get<
DataRelayer>().processDanglingInputs(context.expirationHandlers, *context.registry,
true);
1703 if (activity.expiredSlots > 0) {
1704 state.lastActiveDataProcessor = &context;
1707 streamContext.completed.clear();
1709 state.lastActiveDataProcessor = &context;
1712 context.postDanglingCallbacks(danglingContext);
1720 state.lastActiveDataProcessor = &context;
1743 timingInfo.timeslice = relayer.getOldestPossibleOutput().timeslice.value;
1744 timingInfo.tfCounter = -1;
1745 timingInfo.firstTForbit = -1;
1747 timingInfo.creation = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
1748 O2_SIGNPOST_EVENT_EMIT(calibration, dpid,
"calibration",
"TimingInfo.keepAtEndOfStream %d", timingInfo.keepAtEndOfStream);
1752 context.preEOSCallbacks(eosContext);
1757 context.postEOSCallbacks(eosContext);
1759 for (
auto& channel : spec.outputChannels) {
1767 if (shouldProcess) {
1768 state.lastActiveDataProcessor = &context;
1772 for (
auto& poller :
state.activeOutputPollers) {
1773 uv_poll_stop(poller);
1781 for (
auto& poller :
state.activeOutputPollers) {
1782 uv_poll_stop(poller);
1798 if (deviceContext.sigusr1Handle) {
1832 "Starting forwarding for incoming messages with oldestTimeslice %zu with copy",
1833 oldestTimeslice.timeslice.value);
1834 std::vector<fair::mq::Parts> forwardedParts(proxy.getNumForwardChannels());
1837 for (
int fi = 0; fi < proxy.getNumForwardChannels(); fi++) {
1838 if (forwardedParts[fi].
Size() == 0) {
1842 auto& parts = forwardedParts[fi];
1843 if (info.policy ==
nullptr) {
1847 O2_SIGNPOST_EVENT_EMIT(forwarding, sid,
"forwardInputs",
"Forwarding to %{public}s %d", info.name.c_str(), fi);
1853 O2_SIGNPOST_EVENT_EMIT(async_queue, aid,
"forwardInputs",
"Queuing forwarding oldestPossible %zu", oldestTimeslice.timeslice.value);
1878 auto getInputTypes = [&info, &context]() -> std::optional<std::vector<InputInfo>> {
1883 auto& parts = info.parts;
1886 std::vector<InputInfo> results;
1888 results.reserve(parts.Size() / 2);
1889 size_t nTotalPayloads = 0;
1893 if (
type != InputType::Invalid &&
length > 1) {
1894 nTotalPayloads +=
length - 1;
1898 for (
size_t pi = 0; pi < parts.Size(); pi += 2) {
1899 auto* headerData = parts.At(pi)->GetData();
1900 auto sih = o2::header::get<SourceInfoHeader*>(headerData);
1901 auto dh = o2::header::get<DataHeader*>(headerData);
1904 info.state = sih->state;
1905 insertInputInfo(pi, 2, InputType::SourceInfo, info.id);
1906 state.lastActiveDataProcessor = &context;
1908 LOGP(
error,
"Found data attached to a SourceInfoHeader");
1912 auto dih = o2::header::get<DomainInfoHeader*>(headerData);
1914 O2_SIGNPOST_EVENT_EMIT(
device, cid,
"handle_data",
"Got DomainInfoHeader with oldestPossibleTimeslice %d", (
int)dih->oldestPossibleTimeslice);
1915 insertInputInfo(pi, 2, InputType::DomainInfo, info.id);
1916 state.lastActiveDataProcessor = &context;
1918 LOGP(
error,
"Found data attached to a DomainInfoHeader");
1923 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1927 if (dh->payloadSize > parts.At(pi + 1)->GetSize()) {
1928 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1932 auto dph = o2::header::get<DataProcessingHeader*>(headerData);
1937 O2_SIGNPOST_START(parts,
pid,
"parts",
"Processing DataHeader %{public}-4s/%{public}-16s/%d with splitPayloadParts %d and splitPayloadIndex %d",
1938 dh->dataOrigin.str, dh->dataDescription.str, dh->subSpecification, dh->splitPayloadParts, dh->splitPayloadIndex);
1940 insertInputInfo(pi, 2, InputType::Invalid, info.id);
1944 if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
1947 insertInputInfo(pi, dh->splitPayloadParts + 1, InputType::Data, info.id);
1948 pi += dh->splitPayloadParts - 1;
1954 size_t finalSplitPayloadIndex = pi + (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
1955 if (finalSplitPayloadIndex > parts.Size()) {
1957 insertInputInfo(pi, 0, InputType::Invalid, info.id);
1960 insertInputInfo(pi, 2, InputType::Data, info.id);
1961 for (; pi + 2 < finalSplitPayloadIndex; pi += 2) {
1962 insertInputInfo(pi + 2, 2, InputType::Data, info.id);
1966 if (results.size() + nTotalPayloads != parts.Size()) {
1967 O2_SIGNPOST_EVENT_EMIT_ERROR(
device, cid,
"handle_data",
"inconsistent number of inputs extracted. %zu vs parts (%zu)", results.size() + nTotalPayloads, parts.Size());
1968 return std::nullopt;
1973 auto reportError = [
ref](
const char*
message) {
1978 auto handleValidMessages = [&info,
ref, &reportError, &context](std::vector<InputInfo>
const& inputInfos) {
1982 auto& parts = info.parts;
1985 bool hasBackpressure =
false;
1986 size_t minBackpressureTimeslice = -1;
1988 size_t oldestPossibleTimeslice = -1;
1989 static std::vector<int> ordering;
1991 ordering.resize(inputInfos.size());
1992 std::iota(ordering.begin(), ordering.end(), 0);
1994 std::stable_sort(ordering.begin(), ordering.end(), [&inputInfos](
int const&
a,
int const&
b) {
1995 auto const& ai = inputInfos[a];
1996 auto const& bi = inputInfos[b];
1997 if (ai.type != bi.type) {
1998 return ai.type < bi.type;
2000 return ai.position < bi.position;
2002 for (
size_t ii = 0; ii < inputInfos.size(); ++ii) {
2003 auto const& input = inputInfos[ordering[ii]];
2004 switch (input.type) {
2005 case InputType::Data: {
2007 auto headerIndex = input.position;
2009 auto nPayloadsPerHeader = 0;
2010 if (input.size > 2) {
2012 nMessages = input.size;
2013 nPayloadsPerHeader = nMessages - 1;
2016 auto dh = o2::header::get<DataHeader*>(parts.At(headerIndex)->GetData());
2017 nMessages = dh->splitPayloadParts > 0 ? dh->splitPayloadParts * 2 : 2;
2018 nPayloadsPerHeader = 1;
2019 ii += (nMessages / 2) - 1;
2023 O2_SIGNPOST_EVENT_EMIT(async_queue, cid,
"onDrop",
"Dropping message from slot %zu. Forwarding as needed. Timeslice %zu",
2024 slot.
index, oldestOutputInfo.timeslice.value);
2031 forwardInputs(
ref, slot, dropped, oldestOutputInfo,
false,
true);
2034 auto relayed = relayer.relay(parts.At(headerIndex)->GetData(),
2035 &parts.At(headerIndex),
2041 switch (relayed.type) {
2043 if (info.normalOpsNotified ==
true && info.backpressureNotified ==
false) {
2044 LOGP(alarm,
"Backpressure on channel {}. Waiting.", info.channel->GetName());
2045 auto& monitoring =
ref.get<o2::monitoring::Monitoring>();
2046 monitoring.send(o2::monitoring::Metric{1, fmt::format(
"backpressure_{}", info.channel->GetName())});
2047 info.backpressureNotified =
true;
2048 info.normalOpsNotified =
false;
2050 policy.backpressure(info);
2051 hasBackpressure =
true;
2052 minBackpressureTimeslice = std::min<size_t>(minBackpressureTimeslice, relayed.timeslice.value);
2057 if (info.normalOpsNotified ==
false && info.backpressureNotified ==
true) {
2058 LOGP(info,
"Back to normal on channel {}.", info.channel->GetName());
2059 auto& monitoring =
ref.get<o2::monitoring::Monitoring>();
2060 monitoring.send(o2::monitoring::Metric{0, fmt::format(
"backpressure_{}", info.channel->GetName())});
2061 info.normalOpsNotified =
true;
2062 info.backpressureNotified =
false;
2067 case InputType::SourceInfo: {
2068 LOGP(detail,
"Received SourceInfo");
2070 state.lastActiveDataProcessor = &context;
2071 auto headerIndex = input.position;
2072 auto payloadIndex = input.position + 1;
2073 assert(payloadIndex < parts.Size());
2076 parts.At(headerIndex).reset(
nullptr);
2077 parts.At(payloadIndex).reset(
nullptr);
2084 case InputType::DomainInfo: {
2088 state.lastActiveDataProcessor = &context;
2089 auto headerIndex = input.position;
2090 auto payloadIndex = input.position + 1;
2091 assert(payloadIndex < parts.Size());
2095 auto dih = o2::header::get<DomainInfoHeader*>(parts.At(headerIndex)->GetData());
2096 if (hasBackpressure && dih->oldestPossibleTimeslice >= minBackpressureTimeslice) {
2099 oldestPossibleTimeslice = std::min(oldestPossibleTimeslice, dih->oldestPossibleTimeslice);
2100 LOGP(
debug,
"Got DomainInfoHeader, new oldestPossibleTimeslice {} on channel {}", oldestPossibleTimeslice, info.id.value);
2101 parts.At(headerIndex).reset(
nullptr);
2102 parts.At(payloadIndex).reset(
nullptr);
2104 case InputType::Invalid: {
2105 reportError(
"Invalid part found.");
2111 if (oldestPossibleTimeslice != (
size_t)-1) {
2112 info.oldestForChannel = {oldestPossibleTimeslice};
2114 context.domainInfoUpdatedCallback(*context.registry, oldestPossibleTimeslice, info.id);
2116 state.lastActiveDataProcessor = &context;
2118 auto it = std::remove_if(parts.fParts.begin(), parts.fParts.end(), [](
auto&
msg) ->
bool { return msg.get() == nullptr; });
2119 parts.fParts.erase(it, parts.end());
2120 if (parts.fParts.size()) {
2121 LOG(
debug) << parts.fParts.size() <<
" messages backpressured";
2133 auto inputTypes = getInputTypes();
2134 if (
bool(inputTypes) ==
false) {
2135 reportError(
"Parts should come in couples. Dropping it.");
2138 handleValidMessages(*inputTypes);
2144struct InputLatency {
2149auto calculateInputRecordLatency(
InputRecord const& record, uint64_t currentTime) -> InputLatency
2153 for (
auto& item : record) {
2154 auto*
header = o2::header::get<DataProcessingHeader*>(item.header);
2155 if (header ==
nullptr) {
2158 int64_t partLatency = (0x7fffffffffffffff & currentTime) - (0x7fffffffffffffff &
header->creation);
2159 if (partLatency < 0) {
2162 result.minLatency = std::min(
result.minLatency, (uint64_t)partLatency);
2163 result.maxLatency = std::max(
result.maxLatency, (uint64_t)partLatency);
2168auto calculateTotalInputRecordSize(
InputRecord const& record) ->
int
2170 size_t totalInputSize = 0;
2171 for (
auto& item : record) {
2172 auto*
header = o2::header::get<DataHeader*>(item.header);
2173 if (header ==
nullptr) {
2176 totalInputSize +=
header->payloadSize;
2178 return totalInputSize;
2181template <
typename T>
2182void update_maximum(std::atomic<T>& maximum_value, T
const&
value)
noexcept
2184 T prev_value = maximum_value;
2185 while (prev_value <
value &&
2186 !maximum_value.compare_exchange_weak(prev_value,
value)) {
2194 LOGP(
debug,
"DataProcessingDevice::tryDispatchComputation");
2199 std::vector<std::span<fair::mq::MessagePtr>> currentSetOfInputs;
2200 std::vector<std::vector<fair::mq::MessagePtr>> ownedInputs;
2203 auto getInputSpan = [
ref, ¤tSetOfInputs, &ownedInputs](
TimesliceSlot slot,
bool consume =
true) {
2208 ownedInputs = relayer.consumeExistingInputsForTimeslice(slot);
2210 currentSetOfInputs.resize(ownedInputs.size());
2211 for (
size_t i = 0;
i < ownedInputs.size(); ++
i) {
2212 currentSetOfInputs[
i] = std::span(ownedInputs[
i]);
2217 auto const& msgs = currentSetOfInputs[
i];
2218 if (msgs.size() <=
indices.headerIdx) {
2221 auto const& headerMsg = msgs[
indices.headerIdx];
2222 char const* payloadData =
nullptr;
2223 size_t payloadSize = 0;
2224 if (msgs.size() >
indices.payloadIdx && msgs[
indices.payloadIdx]) {
2225 payloadData =
static_cast<char const*
>(msgs[
indices.payloadIdx]->GetData());
2226 payloadSize = msgs[
indices.payloadIdx]->GetSize();
2229 headerMsg ?
static_cast<char const*
>(headerMsg->GetData()) :
nullptr,
2233 auto nofPartsGetter = [¤tSetOfInputs](
size_t i) ->
size_t {
2236 auto refCountGetter = [¤tSetOfInputs](
size_t idx) ->
int {
2237 auto&
header =
static_cast<const fair::mq::shmem::Message&
>(*(currentSetOfInputs[idx] |
get_header{0}));
2238 return header.GetRefCount();
2242 return next.headerIdx < currentSetOfInputs[
i].size() ? next :
DataRefIndices{size_t(-1), size_t(-1)};
2244 auto payloadGetter = [¤tSetOfInputs](
size_t i,
DataRefIndices current) -> fair::mq::Message* {
2245 auto const& msgs = currentSetOfInputs[
i];
2246 if (msgs.size() <= current.payloadIdx || !msgs[current.payloadIdx]) {
2249 return msgs[current.payloadIdx].get();
2251 return InputSpan{nofPartsGetter, refCountGetter, indicesGetter, nextIndicesGetter, payloadGetter, currentSetOfInputs.
size()};
2266 auto timeslice = relayer.getTimesliceForSlot(
i);
2268 timingInfo.timeslice = timeslice.value;
2278 auto timeslice = relayer.getTimesliceForSlot(
i);
2280 timingInfo.globalRunNumberChanged = !
TimingInfo::timesliceIsTimer(timeslice.value) && dataProcessorContext.lastRunNumberProcessed != timingInfo.runNumber;
2282 timingInfo.globalRunNumberChanged &= (dataProcessorContext.lastRunNumberProcessed == -1 || timingInfo.runNumber != 0);
2286 timingInfo.streamRunNumberChanged = timingInfo.globalRunNumberChanged;
2294 assert(record.size() == currentSetOfInputs.size());
2295 for (
size_t ii = 0, ie = record.size(); ii < ie; ++ii) {
2299 DataRef input = record.getByPos(ii);
2303 if (input.
header ==
nullptr) {
2308 ownedInputs[ii].clear();
2309 currentSetOfInputs[ii] = {};
2320 for (
size_t pi = 0, pe = record.size(); pi < pe; ++pi) {
2321 DataRef input = record.getByPos(pi);
2322 if (input.
header ==
nullptr) {
2325 auto sih = o2::header::get<SourceInfoHeader*>(input.
header);
2330 auto dh = o2::header::get<DataHeader*>(input.
header);
2340 if (dh->splitPayloadParts > 0 && dh->splitPayloadParts == dh->splitPayloadIndex) {
2343 pi += dh->splitPayloadParts - 1;
2345 size_t pi = pi + (dh->splitPayloadParts > 0 ? dh->splitPayloadParts : 1) * 2;
2351 if (completed.empty() ==
true) {
2352 LOGP(
debug,
"No computations available for dispatching.");
2361 std::atomic_thread_fence(std::memory_order_release);
2362 char relayerSlotState[1024];
2363 int written = snprintf(relayerSlotState, 1024,
"%d ", pipelineLength);
2364 char*
buffer = relayerSlotState + written;
2365 for (
size_t ai = 0; ai != record.size(); ai++) {
2366 buffer[ai] = record.isValid(ai) ?
'3' :
'0';
2368 buffer[record.size()] = 0;
2370 .size = (int)(record.size() +
buffer - relayerSlotState),
2371 .
data = relayerSlotState});
2374 int64_t wallTimeMs = (tEnd - tStart) / 1000000;
2382 auto latency = calculateInputRecordLatency(record, tStartMilli);
2385 static int count = 0;
2392 std::atomic_thread_fence(std::memory_order_release);
2393 char relayerSlotState[1024];
2394 snprintf(relayerSlotState, 1024,
"%d ", pipelineLength);
2395 char*
buffer = strchr(relayerSlotState,
' ') + 1;
2396 for (
size_t ai = 0; ai != record.size(); ai++) {
2397 buffer[ai] = record.isValid(ai) ?
'2' :
'0';
2399 buffer[record.size()] = 0;
2417 switch (spec.completionPolicy.order) {
2419 std::sort(completed.begin(), completed.end(), [](
auto const&
a,
auto const&
b) { return a.timeslice.value < b.timeslice.value; });
2422 std::sort(completed.begin(), completed.end(), [](
auto const&
a,
auto const&
b) { return a.slot.index < b.slot.index; });
2429 for (
auto action : completed) {
2431 O2_SIGNPOST_START(
device, aid,
"device",
"Processing action on slot %lu for action %{public}s", action.slot.index, fmt::format(
"{}", action.op).c_str());
2439 prepareAllocatorForCurrentTimeSlice(
TimesliceSlot{action.slot});
2445 InputSpan span = getInputSpan(action.slot, shouldConsume);
2455 dpContext.preProcessingCallbacks(processContext);
2458 context.postDispatchingCallbacks(processContext);
2459 if (spec.forwards.empty() ==
false) {
2461 forwardInputs(
ref, action.slot, currentSetOfInputs, timesliceIndex.getOldestPossibleOutput(),
false);
2470 bool hasForwards = spec.forwards.empty() ==
false;
2490 markInputsAsDone(action.slot);
2494 preUpdateStats(action, record, tStart);
2496 static bool noCatch = getenv(
"O2_NO_CATCHALL_EXCEPTIONS") && strcmp(getenv(
"O2_NO_CATCHALL_EXCEPTIONS"),
"0");
2504 switch (action.op) {
2515 if (
state.quitRequested ==
false) {
2519 streamContext.preProcessingCallbacks(processContext);
2525 if (context.statefulProcess && shouldProcess(action)) {
2529 (context.statefulProcess)(processContext);
2531 }
else if (context.statelessProcess && shouldProcess(action)) {
2533 (context.statelessProcess)(processContext);
2535 }
else if (context.statelessProcess || context.statefulProcess) {
2541 if (shouldProcess(action)) {
2543 if (timingInfo.globalRunNumberChanged) {
2544 context.lastRunNumberProcessed = timingInfo.runNumber;
2561 streamContext.finaliseOutputsCallbacks(processContext);
2567 streamContext.postProcessingCallbacks(processContext);
2573 state.severityStack.push_back((
int)fair::Logger::GetConsoleSeverity());
2574 fair::Logger::SetConsoleSeverity(fair::Severity::trace);
2580 (context.errorHandling)(
e, record);
2585 }
catch (std::exception& ex) {
2590 (context.errorHandling)(
e, record);
2592 (context.errorHandling)(
e, record);
2595 if (
state.severityStack.empty() ==
false) {
2596 fair::Logger::SetConsoleSeverity((fair::Severity)
state.severityStack.back());
2597 state.severityStack.pop_back();
2600 postUpdateStats(action, record, tStart, tStartMilli);
2604 cleanupRecord(record);
2605 context.postDispatchingCallbacks(processContext);
2613 context.postForwardingCallbacks(processContext);
2615 cleanTimers(action.slot, record);
2617 O2_SIGNPOST_END(
device, aid,
"device",
"Done processing action on slot %lu for action %{public}s", action.slot.index, fmt::format(
"{}", action.op).c_str());
2623 LOGP(detail,
"Broadcasting end of stream");
2624 for (
auto& channel : spec.outputChannels) {
2647 cfg.getRecursive(
name);
2648 std::vector<std::unique_ptr<ParamRetriever>> retrievers;
2649 retrievers.emplace_back(std::make_unique<ConfigurationOptionsRetriever>(&cfg,
name));
2650 auto configStore = std::make_unique<ConfigParamStore>(options, std::move(retrievers));
2651 configStore->preload();
2652 configStore->activate();
struct uv_timer_s uv_timer_t
struct uv_signal_s uv_signal_t
struct uv_async_s uv_async_t
struct uv_poll_s uv_poll_t
struct uv_loop_s uv_loop_t
o2::monitoring::Metric Metric
o2::configuration::ConfigurationInterface ConfigurationInterface
constexpr int DEFAULT_MAX_CHANNEL_AHEAD
std::vector< std::string > header
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char events
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events Show standalone event Run event generator maximum time bin of continuous for triggered for automatic continuous for automatic continuous triggered uint64_t
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of< 0:reject at max -n hits") AddOptionRTC(rejectIFCLowRadiusCluster, uint8_t, 1, "", 0, "Reject clusters that get the IFC mask error during refit") AddOptionRTC(dEdxTruncLow, uint8_t, 2, "", 0, "Low truncation threshold, fraction of 128") AddOptionRTC(dEdxTruncHigh, uint8_t, 77, "", 0, "High truncation threshold, fraction of 128") AddOptionRTC(extrapolationTracking, int8_t, 1, "", 0, "Enable Extrapolation Tracking(prolong tracks to adjacent sectors to find short segments)") AddOptionRTC(disableRefitAttachment, uint8_t, 0, "", 0, "Bitmask to disable certain attachment steps during refit(1:attachment, 2:propagation, 4:loop following, 8:mirroring)") AddOptionRTC(rejectionStrategy, uint8_t, o2::gpu::GPUSettings::RejectionStrategyA, "", 0, "Enable rejection of TPC clusters for compression(0=no, 1=strategy A, 2=strategy B)") AddOptionRTC(mergeLoopersAfterburner, uint8_t, 1, "", 0, "Run afterburner for additional looper merging") AddOptionRTC(compressionTypeMask, uint8_t, o2::gpu::GPUSettings::CompressionFull, "", 0, "TPC Compression mode bits(1=truncate charge/width LSB, 2=differences, 4=track-model)") AddOptionRTC(compressionSortOrder, uint8_t, o2::gpu::GPUSettings::SortTime, "", 0, "Sort order of TPC compression(0=time, 1=pad, 2=Z-time-pad, 3=Z-pad-time, 4=no sorting(use incoming order))") AddOptionRTC(sigBitsCharge, uint8_t, 4, "", 0, "Number of significant bits for TPC cluster charge in compression mode 1") AddOptionRTC(sigBitsWidth, uint8_t, 3, "", 0, "Number of significant bits for TPC cluster width in compression mode 1") AddOptionRTC(dropLoopers, uint8_t, 0, "", 0, "Drop looping tracks starting from second loop") AddOptionRTC(mergerInterpolateErrors, uint8_t, 1, "", 0, "Use interpolation instead of extrapolation for chi2 based cluster rejection") AddOptionRTC(mergerInterpolateRejectAlsoOnCurrentPosition, uint8_t, 1, "", 0, "When using mergerInterpolateErrors, reject based on chi2 twice computed with interpolated and current track position starting from NDF > mergerNonInterpolateRejectMinNDF Merge tracks accross the central electrode Enable PID response Add sub threshold clusters in TPC dEdx computation OR mask of TPC flags that will reject the cluster in alternative dEdx Reject edge clusters based on uncorrected track Y during track fit Increase decrease removal by when on this number of pads from the edge h configStandalone Reconstruction rec_trd Max chi2 for TRD tracklets to be matched to a track Chi2 cut for strict matching mode Number of sigmas for ITS TPC track time error estimate Addition to search road around track prolongation along Y in cm Optimal tracklet rphi resolution in maximum chi2 per attached tracklet for TRD tracks take into account the full likelihood of the slope including exponential tails Tracks with less attached tracklets are discarded after the tracking Post trigger Pile up integration time in BCs fits all iterations but the last one in projections Use material lookup table for TPC refit Apply workarounds to avoid cyclic merge should not be needed tpc dyn configStandalone Processing proc_rtc Replace constant variables static by constexpr expressions Compile RTC in deterministic with NO_FAST_MATH flags and GPUCA_DETERMINISTIC_MODE define Use RTC to optimize GPU code h configStandalone Processing proc_rtctech Use a file lock to serialize access to the cache folder Print launch bounds used for RTC code as debugging for exit after Allow RTC with slave GPUReconstruction instances with optConstexpr and optSpecialcode Maximum number of RTC cache files per backend in the cache the oldest are Prepend RTC compilation commands by this string Load a parameter object containing the launch bounds from a file h configStandalone Processing proc_param TPC Cluster Z Error Parameterization h configStandalone Processing settings for neural network proc_nn std::string Specify inference device(cpu(default), rocm, cuda)") AddOption(nnInferenceDeviceId
char error[CURL_ERROR_SIZE]
std::enable_if_t< std::is_signed< T >::value, bool > hasData(const CalArray< T > &cal)
#define O2_SIGNPOST_EVENT_EMIT_ERROR(log, id, name, format,...)
#define O2_DECLARE_DYNAMIC_LOG(name)
#define O2_SIGNPOST_ID_FROM_POINTER(name, log, pointer)
#define O2_SIGNPOST_END(log, id, name, format,...)
#define O2_LOG_ENABLED(log)
#define O2_SIGNPOST_ID_GENERATE(name, log)
#define O2_SIGNPOST_EVENT_EMIT_WARN(log, id, name, format,...)
#define O2_SIGNPOST_EVENT_EMIT(log, id, name, format,...)
#define O2_SIGNPOST_START(log, id, name, format,...)
constexpr uint32_t runtime_hash(char const *str)
o2::monitoring::Monitoring Monitoring
@ DeviceStateChanged
Invoked the device undergoes a state change.
decltype(auto) make(const Output &spec, Args... args)
static void doRun(ServiceRegistryRef)
void fillContext(DataProcessorContext &context, DeviceContext &deviceContext)
void error(const char *msg)
DataProcessingDevice(RunningDeviceRef ref, ServiceRegistry &)
static void doPrepare(ServiceRegistryRef)
static bool tryDispatchComputation(ServiceRegistryRef ref, std::vector< DataRelayer::RecordAction > &completed)
static void handleData(ServiceRegistryRef, InputChannelInfo &)
uint32_t getFirstTFOrbitForSlot(TimesliceSlot slot)
Get the firstTForbit associate to a given slot.
void updateCacheStatus(TimesliceSlot slot, CacheEntryStatus oldStatus, CacheEntryStatus newStatus)
uint32_t getRunNumberForSlot(TimesliceSlot slot)
Get the runNumber associated to a given slot.
void prunePending(OnDropCallback)
Prune all the pending entries in the cache.
std::vector< std::vector< fair::mq::MessagePtr > > consumeAllInputsForTimeslice(TimesliceSlot id)
uint64_t getCreationTimeForSlot(TimesliceSlot slot)
Get the creation time associated to a given slot.
ActivityStats processDanglingInputs(std::vector< ExpirationHandler > const &, ServiceRegistryRef context, bool createNew)
uint32_t getFirstTFCounterForSlot(TimesliceSlot slot)
Get the firstTFCounter associate to a given slot.
A service API to communicate with the driver.
virtual fair::mq::Device * device()=0
bool active() const
Check if service of type T is currently active.
OldestOutputInfo getOldestPossibleOutput() const
GLuint const GLchar * name
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * value
GLint GLint GLsizei GLint GLenum GLenum type
GLuint GLsizei GLsizei * length
GLuint GLsizei const GLchar * label
GLsizei GLenum const void * indices
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLuint GLsizei const GLchar * message
GLboolean GLboolean GLboolean GLboolean a
Defining ITS Vertex explicitly as messageable.
auto decongestionCallbackLate
RuntimeErrorRef runtime_error(const char *)
ServiceKind
The kind of service we are asking for.
void on_idle_timer(uv_timer_t *handle)
@ DPL
The channel is a normal input channel.
void run_completion(uv_work_t *handle, int status)
void on_socket_polled(uv_poll_t *poller, int status, int events)
void run_callback(uv_work_t *handle)
auto forwardOnInsertion(ServiceRegistryRef &ref, std::span< fair::mq::MessagePtr > &messages) -> void
volatile int region_read_global_dummy_variable
void handleRegionCallbacks(ServiceRegistryRef registry, std::vector< fair::mq::RegionInfo > &infos)
Invoke the callbacks for the mPendingRegionInfos.
void on_out_of_band_polled(uv_poll_t *poller, int status, int events)
DeviceSpec const & getRunningDevice(RunningDeviceRef const &running, ServiceRegistryRef const &services)
@ EndOfStreaming
End of streaming requested, but not notified.
@ Streaming
Data is being processed.
@ Idle
End of streaming notified.
void on_communication_requested(uv_async_t *s)
@ Expired
A transition needs to be fullfilled ASAP.
@ NoTransition
No pending transitions.
@ Requested
A transition was notified to be requested.
@ AtCompletionPolicySatisified
RuntimeError & error_from_ref(RuntimeErrorRef)
void on_awake_main_thread(uv_async_t *handle)
@ SHM_OFFER_BYTES_CONSUMED
@ TIMESLICE_NUMBER_EXPIRED
@ TIMESLICE_OFFER_NUMBER_CONSUMED
@ Completed
The channel was signaled it will not receive any data.
@ Running
The channel is actively receiving data.
void on_signal_callback(uv_signal_t *handle, int signum)
@ Me
Only quit this data processor.
constexpr const char * channelName(int channel)
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
static void run(AsyncQueue &queue, TimesliceId oldestPossibleTimeslice)
static void post(AsyncQueue &queue, AsyncTask const &task)
An actuatual task to be executed.
static void demangled_backtrace_symbols(void **backtrace, unsigned int total, int fd)
static constexpr int INVALID
CompletionOp
Action to take with the InputRecord:
@ Retry
Like Wait but mark the cacheline as dirty.
int64_t timeslices
How many timeslices it can process without giving back control.
int64_t sharedMemory
How much shared memory it can allocate.
Statistics on the offers consumed, expired.
static bool hasOnlyGenerated(DeviceSpec const &spec)
check if spec is a source devide
static std::vector< fair::mq::Parts > routeForwardedMessageSet(FairMQDeviceProxy &proxy, std::vector< std::span< fair::mq::MessagePtr > > ¤tSetOfInputs, bool copy, bool consume)
Helper to route messages for forwarding.
static TransitionHandlingState updateStateTransition(ServiceRegistryRef const &ref, ProcessingPolicies const &policies)
starts the EoS timers and returns the new TransitionHandlingState in case as new state is requested
static void switchState(ServiceRegistryRef const &ref, StreamingState newState)
change the device StreamingState to newState
static void sendEndOfStream(ServiceRegistryRef const &ref, OutputChannelSpec const &channel)
static bool sendOldestPossibleTimeframe(ServiceRegistryRef const &ref, ForwardChannelInfo const &info, ForwardChannelState &state, size_t timeslice)
static void cleanForwardedMessages(std::span< fair::mq::MessagePtr > ¤tSetOfInputs, bool consume)
static void routeForwardedMessages(FairMQDeviceProxy &proxy, std::span< fair::mq::MessagePtr > ¤tSetOfInputs, std::vector< fair::mq::Parts > &forwardedParts, bool copy, bool consume)
Helper to route messages for forwarding.
Helper struct to hold statistics about the data processing happening.
@ CumulativeRate
Set the value to the specified value if it is positive.
@ Add
Update the rate of the metric given the amount since the last time.
void updateStats(CommandSpec cmd)
std::function< void(o2::framework::RuntimeErrorRef e, InputRecord &record)> errorHandling
ForwardPolicy forwardPolicy
Wether or not the associated DataProcessor can forward things early.
AlgorithmSpec::InitErrorCallback initError
void preLoopCallbacks(ServiceRegistryRef)
Invoke callbacks before we enter the event loop.
void postStopCallbacks(ServiceRegistryRef)
Invoke callbacks on stop.
void preEOSCallbacks(EndOfStreamContext &)
Invoke callbacks to be executed before every EOS user callback invokation.
void preProcessingCallbacks(ProcessingContext &)
Invoke callbacks to be executed before every process method invokation.
ServiceRegistry * registry
void postEOSCallbacks(EndOfStreamContext &)
Invoke callbacks to be executed after every EOS user callback invokation.
AlgorithmSpec::ErrorCallback error
void preStartCallbacks(ServiceRegistryRef)
Invoke callbacks to be executed in PreRun(), before the User Start callbacks.
AlgorithmSpec::ProcessCallback statefulProcess
static std::vector< size_t > createDistinctRouteIndex(std::vector< InputRoute > const &)
@ Invalid
Ownership of the data has been taken.
@ Backpressured
The incoming data was not valid and has been dropped.
@ Dropped
The incoming data was not relayed, because we are backpressured.
static bool partialMatch(InputSpec const &spec, o2::header::DataOrigin const &origin)
static std::string describe(InputSpec const &spec)
static bool match(InputSpec const &spec, ConcreteDataMatcher const &target)
TimesliceIndex::OldestOutputInfo oldestTimeslice
static unsigned int pipelineLength(unsigned int minLength)
get max number of timeslices in the queue
static std::unique_ptr< ConfigParamStore > getConfiguration(ServiceRegistryRef registry, const char *name, std::vector< ConfigParamSpec > const &options)
uv_signal_t * sigusr1Handle
ProcessingPolicies & processingPolicies
int expectedRegionCallbacks
Running state information of a given device.
uv_async_t * awakeMainThread
std::atomic< int64_t > cleanupCount
Forward channel information.
InputChannelInfo * channelInfo
fair::mq::Socket * socket
DataProcessingDevice * device
enum EarlyForwardPolicy earlyForward
Information about the running workflow.
static Salt streamSalt(short streamId, short dataProcessorId)
void lateBindStreamServices(DeviceState &state, fair::mq::ProgOptions &options, ServiceRegistry::Salt salt)
static Salt globalStreamSalt(short streamId)
static Salt globalDeviceSalt()
void * get(ServiceTypeHash typeHash, Salt salt, ServiceKind kind, char const *name=nullptr) const
void finaliseOutputsCallbacks(ProcessingContext &)
Invoke callbacks to be executed after every process method invokation.
void preProcessingCallbacks(ProcessingContext &pcx)
Invoke callbacks to be executed before every process method invokation.
void postProcessingCallbacks(ProcessingContext &pcx)
Invoke callbacks to be executed after every process method invokation.
static int64_t getRealtimeSinceEpochStandalone()
bool keepAtEndOfStream
Wether this kind of data should be flushed during end of stream.
static bool timesliceIsTimer(size_t timeslice)
static TimesliceId getTimeslice(data_matcher::VariableContext const &variables)
void backpressure(InputChannelInfo const &)
locked_execution(ServiceRegistryRef &ref_)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
uint64_t const void const *restrict const msg