Project
Loading...
Searching...
No Matches
runDataProcessing.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 <memory>
14#define BOOST_BIND_GLOBAL_PLACEHOLDERS
15#include <stdexcept>
40#include "DeviceStateHelpers.h"
43#include "Framework/DebugGUI.h"
46#include "Framework/Logger.h"
50#include "Framework/Signpost.h"
71#include "DriverServerContext.h"
73#include "HTTPParser.h"
74#include "DPLWebSocket.h"
75#include "ArrowSupport.h"
77
80#include "DDSConfigHelpers.h"
81#include "O2ControlHelpers.h"
82#include "DeviceSpecHelpers.h"
83#include "GraphvizHelpers.h"
84#include "MermaidHelpers.h"
85#include "PropertyTreeHelpers.h"
88
89#include <Configuration/ConfigurationInterface.h>
90#include <Configuration/ConfigurationFactory.h>
91#include <Monitoring/MonitoringFactory.h>
93
94#include <fairmq/Device.h>
95#include <fairmq/DeviceRunner.h>
96#include <fairmq/shmem/Monitor.h>
97#include <fairmq/ProgOptions.h>
98
99#include <boost/program_options.hpp>
100#include <boost/program_options/options_description.hpp>
101#include <boost/program_options/variables_map.hpp>
102#include <boost/exception/diagnostic_information.hpp>
103#include <boost/property_tree/json_parser.hpp>
104
105#include <uv.h>
106#include <TEnv.h>
107#include <TSystem.h>
108
109#include <cinttypes>
110#include <cstdint>
111#include <cstdio>
112#include <cstdlib>
113#include <cstring>
114#include <csignal>
115#include <iostream>
116#include <map>
117#include <regex>
118#include <set>
119#include <string>
120#include <type_traits>
121#include <tuple>
122#include <chrono>
123#include <utility>
124#include <numeric>
125#include <functional>
126
127#include <fcntl.h>
128#include <netinet/ip.h>
129#include <sys/resource.h>
130#include <sys/select.h>
131#include <sys/socket.h>
132#include <sys/stat.h>
133#include <sys/time.h>
134#include <sys/types.h>
135#include <sys/un.h>
136#include <sys/wait.h>
137#include <unistd.h>
138#include <execinfo.h>
139#include <cfenv>
140#if defined(__linux__) && __has_include(<sched.h>)
141#include <sched.h>
142#elif __has_include(<linux/getcpu.h>)
143#include <linux/getcpu.h>
144#elif __has_include(<cpuid.h>) && (__x86_64__ || __i386__)
145#include <cpuid.h>
146#define CPUID(INFO, LEAF, SUBLEAF) __cpuid_count(LEAF, SUBLEAF, INFO[0], INFO[1], INFO[2], INFO[3])
147#define GETCPU(CPU) \
148 { \
149 uint32_t CPUInfo[4]; \
150 CPUID(CPUInfo, 1, 0); \
151 /* CPUInfo[1] is EBX, bits 24-31 are APIC ID */ \
152 if ((CPUInfo[3] & (1 << 9)) == 0) { \
153 CPU = -1; /* no APIC on chip */ \
154 } else { \
155 CPU = (unsigned)CPUInfo[1] >> 24; \
156 } \
157 if (CPU < 0) \
158 CPU = 0; \
159 }
160#endif
161
162using namespace o2::monitoring;
163using namespace o2::configuration;
164
165using namespace o2::framework;
166namespace bpo = boost::program_options;
167using DataProcessorInfos = std::vector<DataProcessorInfo>;
168using DeviceExecutions = std::vector<DeviceExecution>;
169using DeviceSpecs = std::vector<DeviceSpec>;
170using DeviceInfos = std::vector<DeviceInfo>;
171using DataProcessingStatesInfos = std::vector<DataProcessingStates>;
172using DeviceControls = std::vector<DeviceControl>;
173using DataProcessorSpecs = std::vector<DataProcessorSpec>;
174
175std::vector<DeviceMetricsInfo> gDeviceMetricsInfos;
176
177// FIXME: probably find a better place
178// these are the device options added by the framework, but they can be
179// overloaded in the config spec
180bpo::options_description gHiddenDeviceOptions("Hidden child options");
181
184
185void doBoostException(boost::exception& e, const char*);
187void doUnknownException(std::string const& s, char const*);
188
189char* getIdString(int argc, char** argv)
190{
191 for (int argi = 0; argi < argc; argi++) {
192 if (strcmp(argv[argi], "--id") == 0 && argi + 1 < argc) {
193 return argv[argi + 1];
194 }
195 }
196 return nullptr;
197}
198
199int callMain(int argc, char** argv, int (*mainNoCatch)(int, char**))
200{
201 static bool noCatch = getenv("O2_NO_CATCHALL_EXCEPTIONS") && strcmp(getenv("O2_NO_CATCHALL_EXCEPTIONS"), "0");
202 int result = 1;
203 if (noCatch) {
204 try {
205 result = mainNoCatch(argc, argv);
207 doDPLException(ref, argv[0]);
208 throw;
209 }
210 } else {
211 try {
212 // The 0 here is an int, therefore having the template matching in the
213 // SFINAE expression above fit better the version which invokes user code over
214 // the default one.
215 // The default policy is a catch all pub/sub setup to be consistent with the past.
216 result = mainNoCatch(argc, argv);
217 } catch (boost::exception& e) {
218 doBoostException(e, argv[0]);
219 throw;
220 } catch (std::exception const& error) {
221 doUnknownException(error.what(), argv[0]);
222 throw;
224 doDPLException(ref, argv[0]);
225 throw;
226 } catch (...) {
227 doUnknownException("", argv[0]);
228 throw;
229 }
230 }
231 return result;
232}
233
234// Read from a given fd and print it.
235// return true if we can still read from it,
236// return false if we need to close the input pipe.
237//
238// FIXME: We should really print full lines.
239void getChildData(int infd, DeviceInfo& outinfo)
240{
241 char buffer[1024 * 16];
242 int bytes_read;
243 // NOTE: do not quite understand read ends up blocking if I read more than
244 // once. Oh well... Good enough for now.
245 int64_t total_bytes_read = 0;
246 int64_t count = 0;
247 bool once = false;
248 while (true) {
249 bytes_read = read(infd, buffer, 1024 * 16);
250 if (bytes_read == 0) {
251 return;
252 }
253 if (!once) {
254 once = true;
255 }
256 if (bytes_read < 0) {
257 return;
258 }
259 assert(bytes_read > 0);
260 outinfo.unprinted.append(buffer, bytes_read);
261 count++;
262 }
263}
264
268bool checkIfCanExit(std::vector<DeviceInfo> const& infos)
269{
270 if (infos.empty()) {
271 return false;
272 }
273 for (auto& info : infos) {
274 if (info.readyToQuit == false) {
275 return false;
276 }
277 }
278 return true;
279}
280
281// Kill all the active children. Exit code
282// is != 0 if any of the children had an error.
283void killChildren(std::vector<DeviceInfo>& infos, int sig)
284{
285 for (auto& info : infos) {
286 if (info.active == true) {
287 kill(info.pid, sig);
288 }
289 }
290}
291
293bool areAllChildrenGone(std::vector<DeviceInfo>& infos)
294{
295 for (auto& info : infos) {
296 if ((info.pid != 0) && info.active) {
297 return false;
298 }
299 }
300 return true;
301}
302
304namespace
305{
306int calculateExitCode(DriverInfo& driverInfo, DeviceSpecs& deviceSpecs, DeviceInfos& infos)
307{
308 std::regex regexp(R"(^\[([\d+:]*)\]\[\w+\] )");
309 if (!driverInfo.lastError.empty()) {
310 LOGP(error, "SEVERE: DPL driver encountered an error while running.\n{}",
311 driverInfo.lastError);
312 return 1;
313 }
314 for (size_t di = 0; di < deviceSpecs.size(); ++di) {
315 auto& info = infos[di];
316 auto& spec = deviceSpecs[di];
317 if (info.maxLogLevel >= driverInfo.minFailureLevel) {
318 LOGP(error, "SEVERE: Device {} ({}) had at least one message above severity {}: {}",
319 spec.name,
320 info.pid,
321 (int)info.minFailureLevel,
322 std::regex_replace(info.firstSevereError, regexp, ""));
323 return 1;
324 }
325 if (info.exitStatus != 0) {
326 LOGP(error, "SEVERE: Device {} ({}) returned with {}",
327 spec.name,
328 info.pid,
329 info.exitStatus);
330 return info.exitStatus;
331 }
332 }
333 return 0;
334}
335} // namespace
336
337void createPipes(int* pipes)
338{
339 auto p = pipe(pipes);
340
341 if (p == -1) {
342 std::cerr << "Unable to create PIPE: ";
343 switch (errno) {
344 case EFAULT:
345 assert(false && "EFAULT while reading from pipe");
346 break;
347 case EMFILE:
348 std::cerr << "Too many active descriptors";
349 break;
350 case ENFILE:
351 std::cerr << "System file table is full";
352 break;
353 default:
354 std::cerr << "Unknown PIPE" << std::endl;
355 };
356 // Kill immediately both the parent and all the children
357 kill(-1 * getpid(), SIGKILL);
358 }
359}
360
361// We don't do anything in the signal handler but
362// we simply note down the fact a signal arrived.
363// All the processing is done by the state machine.
364volatile sig_atomic_t graceful_exit = false;
365volatile sig_atomic_t forceful_exit = false;
366volatile sig_atomic_t sigchld_requested = false;
367volatile sig_atomic_t double_sigint = false;
368
369static void handle_sigint(int)
370{
371 if (graceful_exit == false) {
372 graceful_exit = true;
373 } else {
374 forceful_exit = true;
375 // We keep track about forceful exiting via
376 // a double SIGINT, so that we do not print
377 // any extra message. This means that if the
378 // forceful_exit is set by the timer, we will
379 // get an error message about each child which
380 // did not gracefully exited.
381 double_sigint = true;
382 }
383}
384
386void cleanupSHM(std::string const& uniqueWorkflowId)
387{
388 using namespace fair::mq::shmem;
389 fair::mq::shmem::Monitor::Cleanup(SessionId{"dpl_" + uniqueWorkflowId}, false);
390}
391
392static void handle_sigchld(int) { sigchld_requested = true; }
393
395 std::string const&,
396 DeviceSpec const& spec,
399 DeviceInfos& deviceInfos,
400 DataProcessingStatesInfos& allStates)
401{
402 LOG(info) << "Starting " << spec.id << " as remote device";
403 DeviceInfo info{
404 .pid = 0,
405 .historyPos = 0,
406 .historySize = 1000,
407 .maxLogLevel = LogParsingHelpers::LogLevel::Debug,
408 .active = true,
409 .readyToQuit = false,
410 .inputChannelMetricsViewIndex = Metric2DViewIndex{"oldest_possible_timeslice", 0, 0, {}},
411 .outputChannelMetricsViewIndex = Metric2DViewIndex{"oldest_possible_output", 0, 0, {}},
412 .lastSignal = uv_hrtime() - 10000000};
413
414 deviceInfos.emplace_back(info);
415 timespec now;
416 clock_gettime(CLOCK_REALTIME, &now);
417 uint64_t offset = now.tv_sec * 1000 - uv_now(loop);
418 allStates.emplace_back(TimingHelpers::defaultRealtimeBaseConfigurator(offset, loop),
420 // Let's add also metrics information for the given device
422}
423
429
430void log_callback(uv_poll_t* handle, int status, int events)
431{
432 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, handle->loop);
433 auto* logContext = reinterpret_cast<DeviceLogContext*>(handle->data);
434 std::vector<DeviceInfo>* infos = logContext->serverContext->infos;
435 DeviceInfo& info = infos->at(logContext->index);
436
437 if (status < 0) {
438 info.active = false;
439 }
440 if (events & UV_READABLE) {
441 getChildData(logContext->fd, info);
442 }
443 if (events & UV_DISCONNECT) {
444 info.active = false;
445 }
446 O2_SIGNPOST_EVENT_EMIT(driver, sid, "loop", "log_callback invoked by poller for device %{xcode:pid}d which is %{public}s%{public}s",
447 info.pid, info.active ? "active" : "inactive",
448 info.active ? " and still has data to read." : ".");
449 if (info.active == false) {
450 uv_poll_stop(handle);
451 }
452 uv_async_send(logContext->serverContext->asyncLogProcessing);
453}
454
456{
457 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, handle->loop);
458 O2_SIGNPOST_EVENT_EMIT(driver, sid, "mainloop", "close_websocket");
459 delete (WSDPLHandler*)handle->data;
460}
461
462void websocket_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf)
463{
464 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, stream->loop);
465 O2_SIGNPOST_EVENT_EMIT(driver, sid, "mainloop", "websocket_callback");
466 auto* handler = (WSDPLHandler*)stream->data;
467 if (nread == 0) {
468 return;
469 }
470 if (nread == UV_EOF) {
471 if (buf->base) {
472 free(buf->base);
473 }
474 uv_read_stop(stream);
476 return;
477 }
478 if (nread < 0) {
479 // FIXME: should I close?
480 LOG(error) << "websocket_callback: Error while reading from websocket";
481 if (buf->base) {
482 free(buf->base);
483 }
484 uv_read_stop(stream);
486 return;
487 }
488 try {
489 LOG(debug3) << "Parsing request with " << handler << " with " << nread << " bytes";
490 parse_http_request(buf->base, nread, handler);
491 if (buf->base) {
492 free(buf->base);
493 }
494 } catch (WSError& e) {
495 LOG(error) << "Error while parsing request: " << e.message;
496 handler->error(e.code, e.message.c_str());
497 free(buf->base);
498 }
499}
500
501static void my_alloc_cb(uv_handle_t*, size_t suggested_size, uv_buf_t* buf)
502{
503 buf->base = (char*)malloc(suggested_size);
504 buf->len = suggested_size;
505}
506
508void ws_connect_callback(uv_stream_t* server, int status)
509{
510 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, server->loop);
511 O2_SIGNPOST_EVENT_EMIT(driver, sid, "mainloop", "websocket_callback");
512 auto* serverContext = reinterpret_cast<DriverServerContext*>(server->data);
513 if (status < 0) {
514 LOGF(error, "New connection error %s\n", uv_strerror(status));
515 // error!
516 return;
517 }
518
519 auto* client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
520 uv_tcp_init(serverContext->loop, client);
521 if (uv_accept(server, (uv_stream_t*)client) == 0) {
522 client->data = new WSDPLHandler((uv_stream_t*)client, serverContext);
523 uv_read_start((uv_stream_t*)client, (uv_alloc_cb)my_alloc_cb, websocket_callback);
524 } else {
525 uv_close((uv_handle_t*)client, nullptr);
526 }
527}
528
530 std::string configuration;
531 int fd;
532};
533
534void stream_config(uv_work_t* req)
535{
536 auto* context = (StreamConfigContext*)req->data;
537 size_t result = write(context->fd, context->configuration.data(), context->configuration.size());
538 if (result != context->configuration.size()) {
539 LOG(error) << "Unable to pass configuration to children";
540 }
541 {
542 auto error = fsync(context->fd);
543 switch (error) {
544 case EBADF:
545 LOGP(error, "EBADF while flushing child stdin");
546 break;
547 case EINVAL:
548 LOGP(error, "EINVAL while flushing child stdin");
549 break;
550 case EINTR:
551 LOGP(error, "EINTR while flushing child stdin");
552 break;
553 case EIO:
554 LOGP(error, "EIO while flushing child stdin");
555 break;
556 default:;
557 }
558 }
559 {
560 auto error = close(context->fd); // Not allowing further communication...
561 switch (error) {
562 case EBADF:
563 LOGP(error, "EBADF while closing child stdin");
564 break;
565 case EINTR:
566 LOGP(error, "EINTR while closing child stdin");
567 break;
568 case EIO:
569 LOGP(error, "EIO while closing child stdin");
570 break;
571 default:;
572 }
573 }
574}
575
576struct DeviceRef {
577 int index;
578};
579
583};
584
586{
587 struct sigaction sa_handle_int;
588 sa_handle_int.sa_handler = handle_sigint;
589 sigemptyset(&sa_handle_int.sa_mask);
590 sa_handle_int.sa_flags = SA_RESTART;
591 if (sigaction(SIGINT, &sa_handle_int, nullptr) == -1) {
592 perror("Unable to install signal handler");
593 exit(1);
594 }
595 struct sigaction sa_handle_term;
596 sa_handle_term.sa_handler = handle_sigint;
597 sigemptyset(&sa_handle_term.sa_mask);
598 sa_handle_term.sa_flags = SA_RESTART;
599 if (sigaction(SIGTERM, &sa_handle_int, nullptr) == -1) {
600 perror("Unable to install signal handler");
601 exit(1);
602 }
603}
604
606 std::string const& forwardedStdin,
607 std::vector<DeviceStdioContext>& childFds,
608 std::vector<uv_poll_t*>& handles)
609{
610 for (size_t i = 0; i < childFds.size(); ++i) {
611 auto& childstdin = childFds[i].childstdin;
612 auto& childstdout = childFds[i].childstdout;
613
614 auto* req = (uv_work_t*)malloc(sizeof(uv_work_t));
615 req->data = new StreamConfigContext{forwardedStdin, childstdin[1]};
616 uv_queue_work(serverContext->loop, req, stream_config, nullptr);
617
618 // Setting them to non-blocking to avoid haing the driver hang when
619 // reading from child.
620 int resultCode = fcntl(childstdout[0], F_SETFL, O_NONBLOCK);
621 if (resultCode == -1) {
622 LOGP(error, "Error while setting the socket to non-blocking: {}", strerror(errno));
623 }
624
626 auto addPoller = [&handles, &serverContext](int index, int fd) {
627 auto* context = new DeviceLogContext{};
628 context->index = index;
629 context->fd = fd;
630 context->serverContext = serverContext;
631 handles.push_back((uv_poll_t*)malloc(sizeof(uv_poll_t)));
632 auto handle = handles.back();
633 handle->data = context;
634 uv_poll_init(serverContext->loop, handle, fd);
635 uv_poll_start(handle, UV_READABLE, log_callback);
636 };
637
638 addPoller(i, childstdout[0]);
639 }
640}
641
642void handle_crash(int sig)
643{
644 // dump demangled stack trace
645 void* array[1024];
646 int size = backtrace(array, 1024);
647
648 {
649 char buffer[1024];
650 char const* msg = "*** Program crashed (%s)\nBacktrace by DPL:\n";
651 snprintf(buffer, 1024, msg, strsignal(sig));
652 if (sig == SIGFPE) {
653 if (std::fetestexcept(FE_DIVBYZERO)) {
654 snprintf(buffer, 1024, msg, "FLOATING POINT EXCEPTION - DIVISION BY ZERO");
655 } else if (std::fetestexcept(FE_INVALID)) {
656 snprintf(buffer, 1024, msg, "FLOATING POINT EXCEPTION - INVALID RESULT");
657 } else {
658 snprintf(buffer, 1024, msg, "FLOATING POINT EXCEPTION - UNKNOWN REASON");
659 }
660 }
661 auto retVal = write(STDERR_FILENO, buffer, strlen(buffer));
662 (void)retVal;
663 }
665 {
666 char const* msg = "Backtrace complete.\n";
667 int len = strlen(msg); /* the byte length of the string */
668
669 auto retVal = write(STDERR_FILENO, msg, len);
670 (void)retVal;
671 fsync(STDERR_FILENO);
672 }
673 _exit(1);
674}
675
680 std::vector<DeviceSpec> const& specs,
681 DriverInfo& driverInfo,
682 std::vector<DeviceControl>&,
683 std::vector<DeviceExecution>& executions,
684 std::vector<DeviceInfo>& deviceInfos,
685 std::vector<DataProcessingStates>& allStates,
686 ServiceRegistryRef serviceRegistry,
687 boost::program_options::variables_map& varmap,
688 std::vector<DeviceStdioContext>& childFds,
689 unsigned parentCPU,
690 unsigned parentNode)
691{
692 // FIXME: this might not work when more than one DPL driver on the same
693 // machine. Hopefully we do not care.
694 // Not how the first port is actually used to broadcast clients.
695 auto& spec = specs[ref.index];
696 auto& execution = executions[ref.index];
697
698 for (auto& service : spec.services) {
699 if (service.preFork != nullptr) {
700 service.preFork(serviceRegistry, DeviceConfig{varmap});
701 }
702 }
703 // If we have a framework id, it means we have already been respawned
704 // and that we are in a child. If not, we need to fork and re-exec, adding
705 // the framework-id as one of the options.
706 pid_t id = 0;
707 id = fork();
708 // We are the child: prepare options and reexec.
709 if (id == 0) {
710 // We allow being debugged and do not terminate on SIGTRAP
711 signal(SIGTRAP, SIG_IGN);
712 // We immediately ignore SIGUSR1 and SIGUSR2 so that we do not
713 // get killed by the parent trying to force stepping children.
714 // We will re-enable them later on, when it is actually safe to
715 // do so.
716 signal(SIGUSR1, SIG_IGN);
717 signal(SIGUSR2, SIG_IGN);
718
719 // This is the child.
720 // For stdout / stderr, we close the read part of the pipe, the
721 // old descriptor, and then replace it with the write part of the pipe.
722 // For stdin, we close the write part of the pipe, the old descriptor,
723 // and then we replace it with the read part of the pipe.
724 // We also close all the filedescriptors for our sibilings.
725 struct rlimit rlim;
726 getrlimit(RLIMIT_NOFILE, &rlim);
727 // We close all FD, but the one which are actually
728 // used to communicate with the driver. This is a bad
729 // idea in the first place, because rlim_cur could be huge
730 // FIXME: I should understand which one is really to be closed and use
731 // CLOEXEC on it.
732 int rlim_cur = std::min((int)rlim.rlim_cur, 10000);
733 for (int i = 0; i < rlim_cur; ++i) {
734 if (childFds[ref.index].childstdin[0] == i) {
735 continue;
736 }
737 if (childFds[ref.index].childstdout[1] == i) {
738 continue;
739 }
740 close(i);
741 }
742 dup2(childFds[ref.index].childstdin[0], STDIN_FILENO);
743 dup2(childFds[ref.index].childstdout[1], STDOUT_FILENO);
744 dup2(childFds[ref.index].childstdout[1], STDERR_FILENO);
745
746 for (auto& service : spec.services) {
747 if (service.postForkChild != nullptr) {
748 service.postForkChild(serviceRegistry);
749 }
750 }
751 for (auto& env : execution.environ) {
752 putenv(strdup(DeviceSpecHelpers::reworkTimeslicePlaceholder(env, spec).data()));
753 }
754 int err = execvp(execution.args[0], execution.args.data());
755 if (err) {
756 perror("Unable to start child process");
757 exit(1);
758 }
759 } else {
760 O2_SIGNPOST_ID_GENERATE(sid, driver);
761 O2_SIGNPOST_EVENT_EMIT(driver, sid, "spawnDevice", "New child at %{pid}d", id);
762 }
763 close(childFds[ref.index].childstdin[0]);
764 close(childFds[ref.index].childstdout[1]);
765 if (varmap.count("post-fork-command")) {
766 auto templateCmd = varmap["post-fork-command"];
767 auto cmd = fmt::format(fmt::runtime(templateCmd.as<std::string>()),
768 fmt::arg("pid", id),
769 fmt::arg("id", spec.id),
770 fmt::arg("cpu", parentCPU),
771 fmt::arg("node", parentNode),
772 fmt::arg("name", spec.name),
773 fmt::arg("timeslice0", spec.inputTimesliceId),
774 fmt::arg("timeslice1", spec.inputTimesliceId + 1),
775 fmt::arg("rank0", spec.rank),
776 fmt::arg("maxRank0", spec.nSlots));
777 int err = system(cmd.c_str());
778 if (err) {
779 LOG(error) << "Post fork command `" << cmd << "` returned with status " << err;
780 }
781 LOG(debug) << "Successfully executed `" << cmd;
782 }
783 // This is the parent. We close the write end of
784 // the child pipe and and keep track of the fd so
785 // that we can later select on it.
786 for (auto& service : spec.services) {
787 if (service.postForkParent != nullptr) {
788 service.postForkParent(serviceRegistry);
789 }
790 }
791
792 LOG(info) << "Starting " << spec.id << " on pid " << id;
793 deviceInfos.push_back({.pid = id,
794 .historyPos = 0,
795 .historySize = 1000,
796 .maxLogLevel = LogParsingHelpers::LogLevel::Debug,
797 .minFailureLevel = driverInfo.minFailureLevel,
798 .active = true,
799 .readyToQuit = false,
800 .inputChannelMetricsViewIndex = Metric2DViewIndex{"oldest_possible_timeslice", 0, 0, {}},
801 .outputChannelMetricsViewIndex = Metric2DViewIndex{"oldest_possible_output", 0, 0, {}},
802 .lastSignal = uv_hrtime() - 10000000});
803 // create the offset using uv_hrtime
804 timespec now;
805 clock_gettime(CLOCK_REALTIME, &now);
806 uint64_t offset = now.tv_sec * 1000 - uv_now(loop);
807 allStates.emplace_back(
810
811 allStates.back().registerState(DataProcessingStates::StateSpec{
812 .name = "data_queries",
813 .stateId = (short)ProcessingStateId::DATA_QUERIES,
814 .sendInitialValue = true,
815 });
816 allStates.back().registerState(DataProcessingStates::StateSpec{
817 .name = "output_matchers",
818 .stateId = (short)ProcessingStateId::OUTPUT_MATCHERS,
819 .sendInitialValue = true,
820 });
821
822 unsigned int pipelineLength = DefaultsHelpers::pipelineLength(DeviceConfig{varmap});
823 for (size_t i = 0; i < pipelineLength; ++i) {
824 allStates.back().registerState(DataProcessingStates::StateSpec{
825 .name = fmt::format("matcher_variables/{}", i),
826 .stateId = static_cast<short>((short)(ProcessingStateId::CONTEXT_VARIABLES_BASE) + i),
827 .minPublishInterval = 200, // if we publish too often we flood the GUI and we are not able to read it in any case
828 .sendInitialValue = true,
829 });
830 }
831
832 for (size_t i = 0; i < pipelineLength; ++i) {
833 allStates.back().registerState(DataProcessingStates::StateSpec{
834 .name = fmt::format("data_relayer/{}", i),
835 .stateId = static_cast<short>((short)(ProcessingStateId::DATA_RELAYER_BASE) + i),
836 .minPublishInterval = 200, // if we publish too often we flood the GUI and we are not able to read it in any case
837 .sendInitialValue = true,
838 });
839 }
840
841 // Let's add also metrics information for the given device
843}
844
846 DriverInfo& driverInfo,
847 DeviceInfos& infos,
848 DeviceSpecs const& specs,
849 DeviceControls& controls)
850{
851 // Display part. All you need to display should actually be in
852 // `infos`.
853 // TODO: split at \n
854 // TODO: update this only once per 1/60 of a second or
855 // things like this.
856 // TODO: have multiple display modes
857 // TODO: graphical view of the processing?
858 assert(infos.size() == controls.size());
859 ParsedMetricMatch metricMatch;
860
861 int processed = 0;
862 for (size_t di = 0, de = infos.size(); di < de; ++di) {
863 DeviceInfo& info = infos[di];
864 DeviceControl& control = controls[di];
865 assert(specs.size() == infos.size());
866 DeviceSpec const& spec = specs[di];
867
868 if (info.unprinted.empty()) {
869 continue;
870 }
871 processed++;
872
873 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, &info);
874 O2_SIGNPOST_START(driver, sid, "bytes_processed", "bytes processed by %{xcode:pid}d", info.pid);
875
876 std::string_view s = info.unprinted;
877 size_t pos = 0;
878 info.history.resize(info.historySize);
879 info.historyLevel.resize(info.historySize);
880
881 while ((pos = s.find("\n")) != std::string::npos) {
882 std::string_view token{s.substr(0, pos)};
883 auto logLevel = LogParsingHelpers::parseTokenLevel(token);
884
885 // Check if the token is a metric from SimpleMetricsService
886 // if yes, we do not print it out and simply store it to be displayed
887 // in the GUI.
888 // Then we check if it is part of our Poor man control system
889 // if yes, we execute the associated command.
890 if (!control.quiet && (token.find(control.logFilter) != std::string::npos) && logLevel >= info.logLevel) {
891 assert(info.historyPos >= 0);
892 assert(info.historyPos < info.history.size());
893 info.history[info.historyPos] = token;
894 info.historyLevel[info.historyPos] = logLevel;
895 info.historyPos = (info.historyPos + 1) % info.history.size();
896 info.logSeq++;
897 fmt::print("[{}:{}]: {}\n", info.pid, spec.id, token);
898 }
899 // We keep track of the maximum log error a
900 // device has seen.
901 bool maxLogLevelIncreased = false;
902 if (logLevel > info.maxLogLevel && logLevel > LogParsingHelpers::LogLevel::Info &&
903 logLevel != LogParsingHelpers::LogLevel::Unknown) {
904 info.maxLogLevel = logLevel;
905 maxLogLevelIncreased = true;
906 }
907 if (logLevel >= driverInfo.minFailureLevel) {
908 info.lastError = token;
909 if (info.firstSevereError.empty() || maxLogLevelIncreased) {
910 info.firstSevereError = token;
911 }
912 }
913 // +1 is to skip the \n
914 s.remove_prefix(pos + 1);
915 }
916 size_t oldSize = info.unprinted.size();
917 info.unprinted = std::string(s);
918 int64_t bytesProcessed = oldSize - info.unprinted.size();
919 O2_SIGNPOST_END(driver, sid, "bytes_processed", "bytes processed by %{xcode:network-size-in-bytes}" PRIi64, bytesProcessed);
920 }
921 if (processed == 0) {
922 O2_SIGNPOST_ID_FROM_POINTER(lid, driver, loop);
923 O2_SIGNPOST_EVENT_EMIT(driver, lid, "mainloop", "processChildrenOutput invoked for nothing!");
924 }
925}
926
927// Process all the sigchld which are pending
928// @return wether or not a given child exited with an error condition.
930{
931 bool hasError = false;
932 while (true) {
933 int status;
934 pid_t pid = waitpid((pid_t)(-1), &status, WNOHANG);
935 if (pid > 0) {
936 // Normal exit
937 int es = WEXITSTATUS(status);
938 if (WIFEXITED(status) == false || es != 0) {
939 // Look for the name associated to the pid in the infos
940 std::string id = "unknown";
941 assert(specs.size() == infos.size());
942 for (size_t ii = 0; ii < infos.size(); ++ii) {
943 if (infos[ii].pid == pid) {
944 id = specs[ii].id;
945 }
946 }
947 // No need to print anything if the user
948 // force quitted doing a double Ctrl-C.
949 if (double_sigint) {
950 } else if (forceful_exit) {
951 LOGP(error, "pid {} ({}) was forcefully terminated after being requested to quit", pid, id);
952 } else {
953 if (WIFSIGNALED(status)) {
954 int exitSignal = WTERMSIG(status);
955 es = exitSignal + 128;
956 LOGP(error, "Workflow crashed - PID {} ({}) was killed abnormally with {} and exited code was set to {}.", pid, id, strsignal(exitSignal), es);
957 } else {
958 es = 128;
959 LOGP(error, "Workflow crashed - PID {} ({}) did not exit correctly however it's not clear why. Exit code forced to {}.", pid, id, es);
960 }
961 }
962 hasError |= true;
963 }
964 for (auto& info : infos) {
965 if (info.pid == pid) {
966 info.active = false;
967 info.exitStatus = es;
968 }
969 }
970 continue;
971 } else {
972 break;
973 }
974 }
975 return hasError;
976}
977
978void doDPLException(RuntimeErrorRef& e, char const* processName)
979{
980 auto& err = o2::framework::error_from_ref(e);
981 if (err.maxBacktrace != 0) {
982 LOGP(fatal,
983 "Unhandled o2::framework::runtime_error reached the top of main of {}, device shutting down."
984 " Reason: {}",
985 processName, err.what);
986 LOGP(error, "Backtrace follow:");
987 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
988 } else {
989 LOGP(fatal,
990 "Unhandled o2::framework::runtime_error reached the top of main of {}, device shutting down."
991 " Reason: {}",
992 processName, err.what);
993 LOGP(error, "Recompile with DPL_ENABLE_BACKTRACE=1 to get more information.");
994 }
995}
996
997void doUnknownException(std::string const& s, char const* processName)
998{
999 if (s.empty()) {
1000 LOGP(fatal, "unknown error while setting up workflow in {}.", processName);
1001 } else {
1002 LOGP(fatal, "error while setting up workflow in {}: {}", processName, s);
1003 }
1004}
1005
1006[[maybe_unused]] AlgorithmSpec dryRun(DeviceSpec const& spec)
1007{
1009 [&routes = spec.outputs](DataAllocator& outputs) {
1010 LOG(info) << "Dry run enforced. Creating dummy messages to simulate computation happended";
1011 for (auto& route : routes) {
1012 auto concrete = DataSpecUtils::asConcreteDataMatcher(route.matcher);
1013 outputs.make<int>(Output{concrete.origin, concrete.description, concrete.subSpec}, 2);
1014 }
1015 })};
1016}
1017
1019{
1020 // LOG(info) << "Process " << getpid() << " is exiting.";
1021}
1022
1023int doChild(int argc, char** argv, ServiceRegistry& serviceRegistry,
1024 DanglingEdgesContext& danglingEdgesContext,
1025 RunningWorkflowInfo const& runningWorkflow,
1027 DriverConfig const& driverConfig,
1028 ProcessingPolicies processingPolicies,
1029 std::string const& defaultDriverClient,
1030 uv_loop_t* loop)
1031{
1032 fair::Logger::SetConsoleColor(false);
1033 fair::Logger::OnFatal([]() { throw runtime_error("Fatal error"); });
1034 DeviceSpec const& spec = runningWorkflow.devices[ref.index];
1035 LOG(info) << "Spawning new device " << spec.id << " in process with pid " << getpid();
1036
1037 fair::mq::DeviceRunner runner{argc, argv};
1038
1039 // Populate options from the command line. Notice that only the options
1040 // declared in the workflow definition are allowed.
1041 runner.AddHook<fair::mq::hooks::SetCustomCmdLineOptions>([&spec, driverConfig, defaultDriverClient](fair::mq::DeviceRunner& r) {
1042 std::string defaultExitTransitionTimeout = "0";
1043 std::string defaultDataProcessingTimeout = "0";
1044 std::string defaultInfologgerMode = "";
1046 if (deploymentMode == o2::framework::DeploymentMode::OnlineDDS) {
1047 defaultExitTransitionTimeout = "40";
1048 defaultDataProcessingTimeout = "20";
1049 defaultInfologgerMode = "infoLoggerD";
1050 } else if (deploymentMode == o2::framework::DeploymentMode::OnlineECS) {
1051 defaultExitTransitionTimeout = "40";
1052 defaultDataProcessingTimeout = "20";
1053 }
1054 boost::program_options::options_description optsDesc;
1056 char const* defaultSignposts = getenv("DPL_SIGNPOSTS");
1057 optsDesc.add_options()("monitoring-backend", bpo::value<std::string>()->default_value("default"), "monitoring backend info") //
1058 ("dpl-stats-min-online-publishing-interval", bpo::value<std::string>()->default_value("0"), "minimum flushing interval for online metrics (in s)") //
1059 ("driver-client-backend", bpo::value<std::string>()->default_value(defaultDriverClient), "backend for device -> driver communicataon: stdout://: use stdout, ws://: use websockets") //
1060 ("infologger-severity", bpo::value<std::string>()->default_value(""), "minimum FairLogger severity to send to InfoLogger") //
1061 ("dpl-tracing-flags", bpo::value<std::string>()->default_value(""), "pipe `|` separate list of events to be traced") //
1062 ("signposts", bpo::value<std::string>()->default_value(defaultSignposts ? defaultSignposts : ""), "comma separated list of signposts to enable") //
1063 ("expected-region-callbacks", bpo::value<std::string>()->default_value("0"), "how many region callbacks we are expecting") //
1064 ("exit-transition-timeout", bpo::value<std::string>()->default_value(defaultExitTransitionTimeout), "how many second to wait before switching from RUN to READY") //
1065 ("error-on-exit-transition-timeout", bpo::value<bool>()->zero_tokens()->default_value(false), "print error instead of warning when exit transition timer expires") //
1066 ("data-processing-timeout", bpo::value<std::string>()->default_value(defaultDataProcessingTimeout), "how many second to wait before stopping data processing and allowing data calibration") //
1067 ("timeframes-rate-limit", bpo::value<std::string>()->default_value("0"), "how many timeframe can be in flight at the same moment (0 disables)") //
1068 ("configuration,cfg", bpo::value<std::string>()->default_value("command-line"), "configuration backend") //
1069 ("infologger-mode", bpo::value<std::string>()->default_value(defaultInfologgerMode), "O2_INFOLOGGER_MODE override") //
1070 ("log-timestamp-us", bpo::value<bool>()->zero_tokens()->default_value(false), "enable microsecond timestamps in log messages");
1071 r.fConfig.AddToCmdLineOptions(optsDesc, true);
1072 });
1073
1074 // This is to control lifetime. All these services get destroyed
1075 // when the runner is done.
1076 std::unique_ptr<SimpleRawDeviceService> simpleRawDeviceService;
1077 std::unique_ptr<DeviceState> deviceState;
1078 std::unique_ptr<ComputingQuotaEvaluator> quotaEvaluator;
1079 std::unique_ptr<FairMQDeviceProxy> deviceProxy;
1080 std::unique_ptr<DeviceContext> deviceContext;
1081
1082 auto afterConfigParsingCallback = [&simpleRawDeviceService,
1083 &runningWorkflow,
1084 ref,
1085 &spec,
1086 &quotaEvaluator,
1087 &serviceRegistry,
1088 &danglingEdgesContext,
1089 &deviceState,
1090 &deviceProxy,
1091 &processingPolicies,
1092 &deviceContext,
1093 &driverConfig,
1094 &loop](fair::mq::DeviceRunner& r) {
1095 ServiceRegistryRef serviceRef = {serviceRegistry};
1096 simpleRawDeviceService = std::make_unique<SimpleRawDeviceService>(nullptr, spec);
1097 serviceRef.registerService(ServiceRegistryHelpers::handleForService<RawDeviceService>(simpleRawDeviceService.get()));
1098
1099 deviceState = std::make_unique<DeviceState>();
1100 deviceState->loop = loop;
1101 deviceState->tracingFlags = DeviceStateHelpers::parseTracingFlags(r.fConfig.GetPropertyAsString("dpl-tracing-flags"));
1102 serviceRef.registerService(ServiceRegistryHelpers::handleForService<DeviceState>(deviceState.get()));
1103
1104 quotaEvaluator = std::make_unique<ComputingQuotaEvaluator>(serviceRef);
1105 serviceRef.registerService(ServiceRegistryHelpers::handleForService<ComputingQuotaEvaluator>(quotaEvaluator.get()));
1106
1107 deviceContext = std::make_unique<DeviceContext>(DeviceContext{.processingPolicies = processingPolicies});
1108 serviceRef.registerService(ServiceRegistryHelpers::handleForService<DeviceSpec const>(&spec));
1109 serviceRef.registerService(ServiceRegistryHelpers::handleForService<RunningWorkflowInfo const>(&runningWorkflow));
1110 serviceRef.registerService(ServiceRegistryHelpers::handleForService<DeviceContext>(deviceContext.get()));
1111 serviceRef.registerService(ServiceRegistryHelpers::handleForService<DriverConfig const>(&driverConfig));
1112 serviceRef.registerService(ServiceRegistryHelpers::handleForService<DanglingEdgesContext>(&danglingEdgesContext));
1113
1114 auto device = std::make_unique<DataProcessingDevice>(ref, serviceRegistry);
1115
1116 serviceRef.get<RawDeviceService>().setDevice(device.get());
1117 r.fDevice = std::move(device);
1118 fair::Logger::SetConsoleColor(false);
1119 if (r.fConfig.GetProperty<bool>("log-timestamp-us")) {
1120 fair::Logger::DefineVerbosity(fair::Verbosity::user1,
1121 fair::VerbositySpec::Make(fair::VerbositySpec::Info::timestamp_us,
1122 fair::VerbositySpec::Info::severity));
1123 fair::Logger::SetVerbosity(fair::Verbosity::user1);
1124 }
1125
1127 for (auto& service : spec.services) {
1128 LOG(debug) << "Declaring service " << service.name;
1129 serviceRegistry.declareService(service, *deviceState.get(), r.fConfig);
1130 }
1131 if (ResourcesMonitoringHelper::isResourcesMonitoringEnabled(spec.resourceMonitoringInterval)) {
1132 serviceRef.get<Monitoring>().enableProcessMonitoring(spec.resourceMonitoringInterval, {PmMeasurement::Cpu, PmMeasurement::Mem, PmMeasurement::Smaps});
1133 }
1134 };
1135
1136 runner.AddHook<fair::mq::hooks::InstantiateDevice>(afterConfigParsingCallback);
1137
1138 auto result = runner.Run();
1139 ServiceRegistryRef serviceRef = {serviceRegistry};
1140 auto& context = serviceRef.get<DataProcessorContext>();
1141 DataProcessorContext::preExitCallbacks(context.preExitHandles, serviceRef);
1142 return result;
1143}
1144
1146 std::string executable;
1147 std::vector<std::string> args;
1148 std::vector<ConfigParamSpec> options;
1149};
1150
1151void gui_callback(uv_timer_s* ctx)
1152{
1153 auto* gui = reinterpret_cast<GuiCallbackContext*>(ctx->data);
1154 if (gui->plugin == nullptr) {
1155 // The gui is not there. Why are we here?
1156 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, ctx->loop);
1157 O2_SIGNPOST_EVENT_EMIT_ERROR(driver, sid, "gui", "GUI timer callback invoked without a GUI plugin.");
1158 uv_timer_stop(ctx);
1159 return;
1160 }
1161 *gui->guiTimerExpired = true;
1162 static int counter = 0;
1163 if ((counter++ % 6000) == 0) {
1164 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, ctx->loop);
1165 O2_SIGNPOST_EVENT_EMIT(driver, sid, "gui", "The GUI callback got called %d times.", counter);
1166 *gui->guiTimerExpired = false;
1167 }
1168 // One interval per GUI invocation, using the loop as anchor.
1169 O2_SIGNPOST_ID_FROM_POINTER(sid, gui, ctx->loop);
1170 O2_SIGNPOST_START(gui, sid, "gui", "gui_callback");
1171
1172 // New version which allows deferred closure of windows
1173 if (gui->plugin->supportsDeferredClose()) {
1174 // For now, there is nothing for which we want to defer the close
1175 // so if the flag is set, we simply exit
1176 if (*(gui->guiQuitRequested)) {
1177 O2_SIGNPOST_END(gui, sid, "gui", "Quit requested by the GUI.");
1178 return;
1179 }
1180 void* draw_data = nullptr;
1181 uint64_t frameStart = uv_hrtime();
1182 uint64_t frameLatency = frameStart - gui->frameLast;
1183
1184 // if less than 15ms have passed reuse old frame
1185 if (frameLatency / 1000000 <= 15) {
1186 draw_data = gui->lastFrame;
1187 O2_SIGNPOST_END(gui, sid, "gui", "Reusing old frame.");
1188 return;
1189 }
1190 // The result of the pollGUIPreRender is used to determine if we
1191 // should quit the GUI, however, the rendering is started in any
1192 // case, so we should complete it.
1193 if (!gui->plugin->pollGUIPreRender(gui->window, (float)frameLatency / 1000000000.0f)) {
1194 *(gui->guiQuitRequested) = true;
1195 }
1196 draw_data = gui->plugin->pollGUIRender(gui->callback);
1197 gui->plugin->pollGUIPostRender(gui->window, draw_data);
1198
1199 uint64_t frameEnd = uv_hrtime();
1200 *(gui->frameCost) = (frameEnd - frameStart) / 1000000.f;
1201 *(gui->frameLatency) = frameLatency / 1000000.f;
1202 gui->frameLast = frameStart;
1203 } else {
1204 void* draw_data = nullptr;
1205
1206 uint64_t frameStart = uv_hrtime();
1207 uint64_t frameLatency = frameStart - gui->frameLast;
1208
1209 // if less than 15ms have passed reuse old frame
1210 if (frameLatency / 1000000 > 15) {
1211 if (!gui->plugin->pollGUIPreRender(gui->window, (float)frameLatency / 1000000000.0f)) {
1212 *(gui->guiQuitRequested) = true;
1213 O2_SIGNPOST_END(gui, sid, "gui", "Reusing old frame.");
1214 return;
1215 }
1216 draw_data = gui->plugin->pollGUIRender(gui->callback);
1217 gui->plugin->pollGUIPostRender(gui->window, draw_data);
1218 } else {
1219 draw_data = gui->lastFrame;
1220 }
1221
1222 if (frameLatency / 1000000 > 15) {
1223 uint64_t frameEnd = uv_hrtime();
1224 *(gui->frameCost) = (frameEnd - frameStart) / 1000000.f;
1225 *(gui->frameLatency) = frameLatency / 1000000.f;
1226 gui->frameLast = frameStart;
1227 }
1228 }
1229 O2_SIGNPOST_END(gui, sid, "gui", "Gui redrawn.");
1230}
1231
1233void single_step_callback(uv_timer_s* ctx)
1234{
1235 auto* infos = reinterpret_cast<DeviceInfos*>(ctx->data);
1236 killChildren(*infos, SIGUSR1);
1237}
1238
1239void force_exit_callback(uv_timer_s* ctx)
1240{
1241 auto* infos = reinterpret_cast<DeviceInfos*>(ctx->data);
1242 killChildren(*infos, SIGKILL);
1243}
1244
1245std::vector<std::regex> getDumpableMetrics()
1246{
1247 auto performanceMetrics = o2::monitoring::ProcessMonitor::getAvailableMetricsNames();
1248 auto dumpableMetrics = std::vector<std::regex>{};
1249 for (const auto& metric : performanceMetrics) {
1250 dumpableMetrics.emplace_back(metric);
1251 }
1252 dumpableMetrics.emplace_back("^arrow-bytes-delta$");
1253 dumpableMetrics.emplace_back("^aod-bytes-read-uncompressed$");
1254 dumpableMetrics.emplace_back("^aod-bytes-read-compressed$");
1255 dumpableMetrics.emplace_back("^aod-file-read-info$");
1256 dumpableMetrics.emplace_back("^aod-largest-object-written$");
1257 dumpableMetrics.emplace_back("^aod-invalid-read-skipped-timeframes$");
1258 dumpableMetrics.emplace_back("^aod-file-open-skipped-files$");
1259 dumpableMetrics.emplace_back("^table-bytes-.*");
1260 dumpableMetrics.emplace_back("^total-timeframes.*");
1261 dumpableMetrics.emplace_back("^device_state.*");
1262 dumpableMetrics.emplace_back("^total_wall_time_ms$");
1263 dumpableMetrics.emplace_back("^ccdb-.*$");
1264 return dumpableMetrics;
1265}
1266
1268{
1269 auto* context = (DriverServerContext*)handle->data;
1270
1271 static auto performanceMetrics = getDumpableMetrics();
1272 std::ofstream file(context->driver->resourcesMonitoringFilename, std::ios::out);
1274 context->driver->metrics, *(context->specs), performanceMetrics,
1275 file);
1276}
1277
1278void dumpRunSummary(DriverServerContext& context, DriverInfo const& driverInfo, DeviceInfos const& infos, DeviceSpecs const& specs)
1279{
1280 if (infos.empty()) {
1281 return;
1282 }
1283 LOGP(info, "## Processes completed. Run summary:");
1284 LOGP(info, "### Devices started: {}", infos.size());
1285 for (size_t di = 0; di < infos.size(); ++di) {
1286 auto& info = infos[di];
1287 auto& spec = specs[di];
1288 if (info.exitStatus) {
1289 LOGP(error, " - Device {}: pid {} (exit {})", spec.name, info.pid, info.exitStatus);
1290 } else {
1291 LOGP(info, " - Device {}: pid {} (exit {})", spec.name, info.pid, info.exitStatus);
1292 }
1293 if (info.exitStatus != 0 && info.firstSevereError.empty() == false) {
1294 LOGP(info, " - First error: {}", info.firstSevereError);
1295 }
1296 if (info.exitStatus != 0 && info.lastError != info.firstSevereError) {
1297 LOGP(info, " - Last error: {}", info.lastError);
1298 }
1299 }
1300 for (auto& summary : *context.summaryCallbacks) {
1301 summary(ServiceMetricsInfo{*context.metrics, *context.specs, *context.infos, context.driver->metrics, driverInfo});
1302 }
1303}
1304
1305auto bindGUIPort = [](DriverInfo& driverInfo, DriverServerContext& serverContext, std::string frameworkId) {
1306 uv_tcp_init(serverContext.loop, &serverContext.serverHandle);
1307
1308 driverInfo.port = 8080 + (getpid() % 30000);
1309
1310 if (getenv("DPL_REMOTE_GUI_PORT")) {
1311 try {
1312 driverInfo.port = stoi(std::string(getenv("DPL_REMOTE_GUI_PORT")));
1313 } catch (std::invalid_argument) {
1314 LOG(error) << "DPL_REMOTE_GUI_PORT not a valid integer";
1315 } catch (std::out_of_range) {
1316 LOG(error) << "DPL_REMOTE_GUI_PORT out of range (integer)";
1317 }
1318 if (driverInfo.port < 1024 || driverInfo.port > 65535) {
1319 LOG(error) << "DPL_REMOTE_GUI_PORT out of range (1024-65535)";
1320 }
1321 }
1322
1323 int result = 0;
1324 struct sockaddr_in* serverAddr = nullptr;
1325
1326 // Do not offer websocket endpoint for devices
1327 // FIXME: this was blocking david's workflows. For now
1328 // there is no point in any case to have devices
1329 // offering a web based API, but it might make sense in
1330 // the future to inspect them via some web based interface.
1331 if (serverContext.isDriver) {
1332 do {
1333 free(serverAddr);
1334 if (driverInfo.port > 64000) {
1335 throw runtime_error_f("Unable to find a free port for the driver. Last attempt returned %d", result);
1336 }
1337 serverAddr = (sockaddr_in*)malloc(sizeof(sockaddr_in));
1338 uv_ip4_addr("0.0.0.0", driverInfo.port, serverAddr);
1339 auto bindResult = uv_tcp_bind(&serverContext.serverHandle, (const struct sockaddr*)serverAddr, 0);
1340 if (bindResult != 0) {
1341 driverInfo.port++;
1342 usleep(1000);
1343 continue;
1344 }
1345 result = uv_listen((uv_stream_t*)&serverContext.serverHandle, 100, ws_connect_callback);
1346 if (result != 0) {
1347 driverInfo.port++;
1348 usleep(1000);
1349 continue;
1350 }
1351 } while (result != 0);
1352 } else if (getenv("DPL_DEVICE_REMOTE_GUI") && !serverContext.isDriver) {
1353 do {
1354 free(serverAddr);
1355 if (driverInfo.port > 64000) {
1356 throw runtime_error_f("Unable to find a free port for the driver. Last attempt returned %d", result);
1357 }
1358 serverAddr = (sockaddr_in*)malloc(sizeof(sockaddr_in));
1359 uv_ip4_addr("0.0.0.0", driverInfo.port, serverAddr);
1360 auto bindResult = uv_tcp_bind(&serverContext.serverHandle, (const struct sockaddr*)serverAddr, 0);
1361 if (bindResult != 0) {
1362 driverInfo.port++;
1363 usleep(1000);
1364 continue;
1365 }
1366 result = uv_listen((uv_stream_t*)&serverContext.serverHandle, 100, ws_connect_callback);
1367 if (result != 0) {
1368 driverInfo.port++;
1369 usleep(1000);
1370 continue;
1371 }
1372 LOG(info) << "Device GUI port: " << driverInfo.port << " " << frameworkId;
1373 } while (result != 0);
1374 }
1375};
1376
1377// This is the handler for the parent inner loop.
1379 WorkflowInfo const& workflowInfo,
1380 DataProcessorInfos const& previousDataProcessorInfos,
1381 CommandInfo const& commandInfo,
1382 DriverControl& driverControl,
1383 DriverInfo& driverInfo,
1384 DriverConfig& driverConfig,
1385 std::vector<DeviceMetricsInfo>& metricsInfos,
1386 std::vector<ConfigParamSpec> const& detectedParams,
1387 boost::program_options::variables_map& varmap,
1388 std::vector<ServiceSpec>& driverServices,
1389 std::string frameworkId)
1390{
1391 RunningWorkflowInfo runningWorkflow{
1392 .uniqueWorkflowId = driverInfo.uniqueWorkflowId,
1393 .shmSegmentId = (int16_t)atoi(varmap["shm-segment-id"].as<std::string>().c_str())};
1394 DeviceInfos infos;
1395 DeviceControls controls;
1396 DataProcessingStatesInfos allStates;
1397 auto* devicesManager = new DevicesManager{.controls = controls, .infos = infos, .specs = runningWorkflow.devices, .messages = {}};
1398 DeviceExecutions deviceExecutions;
1399 DataProcessorInfos dataProcessorInfos = previousDataProcessorInfos;
1400
1401 std::vector<uv_poll_t*> pollHandles;
1402 std::vector<DeviceStdioContext> childFds;
1403
1404 std::vector<ComputingResource> resources;
1405
1406 if (driverInfo.resources != "") {
1407 resources = ComputingResourceHelpers::parseResources(driverInfo.resources);
1408 } else {
1410 }
1411
1412 auto resourceManager = std::make_unique<SimpleResourceManager>(resources);
1413
1414 DebugGUI* debugGUI = nullptr;
1415 void* window = nullptr;
1416 decltype(debugGUI->getGUIDebugger(infos, runningWorkflow.devices, allStates, dataProcessorInfos, metricsInfos, driverInfo, controls, driverControl)) debugGUICallback;
1417
1418 // An empty frameworkId means this is the driver, so we initialise the GUI
1419 auto initDebugGUI = []() -> DebugGUI* {
1420 uv_lib_t supportLib;
1421 int result = 0;
1422#ifdef __APPLE__
1423 result = uv_dlopen("libO2FrameworkGUISupport.dylib", &supportLib);
1424#else
1425 result = uv_dlopen("libO2FrameworkGUISupport.so", &supportLib);
1426#endif
1427 if (result == -1) {
1428 LOG(error) << uv_dlerror(&supportLib);
1429 return nullptr;
1430 }
1431 DPLPluginHandle* (*dpl_plugin_callback)(DPLPluginHandle*);
1432
1433 result = uv_dlsym(&supportLib, "dpl_plugin_callback", (void**)&dpl_plugin_callback);
1434 if (result == -1) {
1435 LOG(error) << uv_dlerror(&supportLib);
1436 return nullptr;
1437 }
1438 DPLPluginHandle* pluginInstance = dpl_plugin_callback(nullptr);
1439 return PluginManager::getByName<DebugGUI>(pluginInstance, "ImGUIDebugGUI");
1440 };
1441
1442 // We initialise this in the driver, because different drivers might have
1443 // different versions of the service
1444 ServiceRegistry serviceRegistry;
1446
1447 if ((driverConfig.batch == false || getenv("DPL_DRIVER_REMOTE_GUI") != nullptr) && frameworkId.empty()) {
1448 debugGUI = initDebugGUI();
1449 if (debugGUI) {
1450 if (driverConfig.batch == false) {
1451 window = debugGUI->initGUI("O2 Framework debug GUI", serviceRegistry);
1452 } else {
1453 window = debugGUI->initGUI(nullptr, serviceRegistry);
1454 }
1455 }
1456 } else if (getenv("DPL_DEVICE_REMOTE_GUI") && !frameworkId.empty()) {
1457 debugGUI = initDebugGUI();
1458 // We never run the GUI on desktop for devices. All
1459 // you can do is to connect to the remote version.
1460 // this is done to avoid having a proliferation of
1461 // GUIs popping up when the variable is set globally.
1462 // FIXME: maybe this is not what we want, but it should
1463 // be ok for now.
1464 if (debugGUI) {
1465 window = debugGUI->initGUI(nullptr, serviceRegistry);
1466 }
1467 }
1468 if (driverConfig.batch == false && window == nullptr && frameworkId.empty()) {
1469 LOG(warn) << "Could not create GUI. Switching to batch mode. Do you have GLFW on your system?";
1470 driverConfig.batch = true;
1471 if (varmap["error-policy"].defaulted()) {
1472 driverInfo.processingPolicies.error = TerminationPolicy::QUIT;
1473 }
1474 }
1475 bool guiQuitRequested = false;
1476 bool hasError = false;
1477
1478 // FIXME: I should really have some way of exiting the
1479 // parent..
1480 DriverState current;
1481 DriverState previous;
1482
1483 uv_loop_t* loop = uv_loop_new();
1484
1485 uv_timer_t* gui_timer = nullptr;
1486
1487 if (!driverConfig.batch) {
1488 gui_timer = (uv_timer_t*)malloc(sizeof(uv_timer_t));
1489 uv_timer_init(loop, gui_timer);
1490 }
1491
1492 std::vector<ServiceMetricHandling> metricProcessingCallbacks;
1493 std::vector<ServiceSummaryHandling> summaryCallbacks;
1494 std::vector<ServicePreSchedule> preScheduleCallbacks;
1495 std::vector<ServicePostSchedule> postScheduleCallbacks;
1496 std::vector<ServiceDriverInit> driverInitCallbacks;
1497 for (auto& service : driverServices) {
1498 if (service.driverStartup == nullptr) {
1499 continue;
1500 }
1501 service.driverStartup(serviceRegistry, DeviceConfig{varmap});
1502 }
1503
1504 ServiceRegistryRef ref{serviceRegistry};
1505 ref.registerService(ServiceRegistryHelpers::handleForService<DevicesManager>(devicesManager));
1506
1507 bool guiTimerExpired = false;
1508 GuiCallbackContext guiContext;
1509 guiContext.plugin = debugGUI;
1510 guiContext.frameLast = uv_hrtime();
1511 guiContext.frameLatency = &driverInfo.frameLatency;
1512 guiContext.frameCost = &driverInfo.frameCost;
1513 guiContext.guiQuitRequested = &guiQuitRequested;
1514 guiContext.guiTimerExpired = &guiTimerExpired;
1515
1516 // This is to make sure we can process metrics, commands, configuration
1517 // changes coming from websocket (or even via any standard uv_stream_t, I guess).
1518 DriverServerContext serverContext{
1519 .registry = {serviceRegistry},
1520 .loop = loop,
1521 .controls = &controls,
1522 .infos = &infos,
1523 .states = &allStates,
1524 .specs = &runningWorkflow.devices,
1525 .metrics = &metricsInfos,
1526 .metricProcessingCallbacks = &metricProcessingCallbacks,
1527 .summaryCallbacks = &summaryCallbacks,
1528 .driver = &driverInfo,
1529 .gui = &guiContext,
1530 .isDriver = frameworkId.empty(),
1531 };
1532
1533 serverContext.serverHandle.data = &serverContext;
1534
1535 uv_timer_t force_step_timer;
1536 uv_timer_init(loop, &force_step_timer);
1537 uv_timer_t force_exit_timer;
1538 uv_timer_init(loop, &force_exit_timer);
1539
1540 bool guiDeployedOnce = false;
1541 bool once = false;
1542
1543 uv_timer_t metricDumpTimer;
1544 metricDumpTimer.data = &serverContext;
1545 bool allChildrenGone = false;
1546 guiContext.allChildrenGone = &allChildrenGone;
1547 O2_SIGNPOST_ID_FROM_POINTER(sid, driver, loop);
1548 O2_SIGNPOST_START(driver, sid, "driver", "Starting driver loop");
1549
1550 // Async callback to process the output of the children, if needed.
1551 serverContext.asyncLogProcessing = (uv_async_t*)malloc(sizeof(uv_async_t));
1552 serverContext.asyncLogProcessing->data = &serverContext;
1553 uv_async_init(loop, serverContext.asyncLogProcessing, [](uv_async_t* handle) {
1554 auto* context = (DriverServerContext*)handle->data;
1555 processChildrenOutput(context->loop, *context->driver, *context->infos, *context->specs, *context->controls);
1556 for (auto* statusHandler : context->statusHandlers) {
1557 for (size_t di = 0; di < context->infos->size(); ++di) {
1558 statusHandler->sendNewLogs(di);
1559 }
1560 }
1561 });
1562
1563 while (true) {
1564 // If control forced some transition on us, we push it to the queue.
1565 if (driverControl.forcedTransitions.empty() == false) {
1566 for (auto transition : driverControl.forcedTransitions) {
1567 driverInfo.states.push_back(transition);
1568 }
1569 driverControl.forcedTransitions.resize(0);
1570 }
1571 // In case a timeout was requested, we check if we are running
1572 // for more than the timeout duration and exit in case that's the case.
1573 {
1574 auto currentTime = uv_hrtime();
1575 uint64_t diff = (currentTime - driverInfo.startTime) / 1000000000LL;
1576 if ((graceful_exit == false) && (driverInfo.timeout > 0) && (diff > driverInfo.timeout)) {
1577 LOG(info) << "Timout ellapsed. Requesting to quit.";
1578 graceful_exit = true;
1579 }
1580 }
1581 // Move to exit loop if sigint was sent we execute this only once.
1582 if (graceful_exit == true && driverInfo.sigintRequested == false) {
1583 driverInfo.sigintRequested = true;
1584 driverInfo.states.resize(0);
1585 driverInfo.states.push_back(DriverState::QUIT_REQUESTED);
1586 }
1587 // If one of the children dies and sigint was not requested
1588 // we should decide what to do.
1589 if (sigchld_requested == true && driverInfo.sigchldRequested == false) {
1590 driverInfo.sigchldRequested = true;
1591 driverInfo.states.push_back(DriverState::HANDLE_CHILDREN);
1592 }
1593 if (driverInfo.states.empty() == false) {
1594 previous = current;
1595 current = driverInfo.states.back();
1596 } else {
1597 current = DriverState::UNKNOWN;
1598 }
1599 driverInfo.states.pop_back();
1600 switch (current) {
1601 case DriverState::BIND_GUI_PORT:
1602 bindGUIPort(driverInfo, serverContext, frameworkId);
1603 break;
1604 case DriverState::INIT:
1605 LOGP(info, "Initialising O2 Data Processing Layer. Driver PID: {}.", getpid());
1606 LOGP(info, "Driver listening on port: {}", driverInfo.port);
1607
1608 // Install signal handler for quitting children.
1609 driverInfo.sa_handle_child.sa_handler = &handle_sigchld;
1610 sigemptyset(&driverInfo.sa_handle_child.sa_mask);
1611 driverInfo.sa_handle_child.sa_flags = SA_RESTART | SA_NOCLDSTOP;
1612 if (sigaction(SIGCHLD, &driverInfo.sa_handle_child, nullptr) == -1) {
1613 perror(nullptr);
1614 exit(1);
1615 }
1616
1619 if (driverInfo.noSHMCleanup) {
1620 LOGP(warning, "Not cleaning up shared memory.");
1621 } else {
1622 cleanupSHM(driverInfo.uniqueWorkflowId);
1623 }
1628 for (auto& callback : driverInitCallbacks) {
1629 callback(serviceRegistry, {varmap});
1630 }
1631 driverInfo.states.push_back(DriverState::RUNNING);
1632 // driverInfo.states.push_back(DriverState::REDEPLOY_GUI);
1633 LOG(info) << "O2 Data Processing Layer initialised. We brake for nobody.";
1634#ifdef NDEBUG
1635 LOGF(info, "Optimised build. O2DEBUG / LOG(debug) / LOGF(debug) / assert statement will not be shown.");
1636#endif
1637 break;
1638 case DriverState::IMPORT_CURRENT_WORKFLOW:
1639 // This state is needed to fill the metadata structure
1640 // which contains how to run the current workflow
1641 dataProcessorInfos = previousDataProcessorInfos;
1642 for (auto const& device : runningWorkflow.devices) {
1643 auto exists = std::find_if(dataProcessorInfos.begin(),
1644 dataProcessorInfos.end(),
1645 [id = device.id](DataProcessorInfo const& info) -> bool { return info.name == id; });
1646 if (exists != dataProcessorInfos.end()) {
1647 continue;
1648 }
1649 std::vector<std::string> channels;
1650 for (auto channel : device.inputChannels) {
1651 channels.push_back(channel.name);
1652 }
1653 for (auto channel : device.outputChannels) {
1654 channels.push_back(channel.name);
1655 }
1656 dataProcessorInfos.push_back(
1658 device.id,
1659 workflowInfo.executable,
1660 workflowInfo.args,
1661 workflowInfo.options,
1662 channels});
1663 }
1664 break;
1665 case DriverState::MATERIALISE_WORKFLOW:
1666 try {
1667 auto workflowState = WorkflowHelpers::verifyWorkflow(workflow);
1668 if (driverConfig.batch == true && varmap["dds"].as<std::string>().empty() && !varmap["dump-workflow"].as<bool>() && workflowState == WorkflowParsingState::Empty) {
1669 LOGP(error, "Empty workflow provided while running in batch mode.");
1670 return 1;
1671 }
1672
1675 auto altered_workflow = workflow;
1676
1677 auto controlParamFromName = [](std::string const& paramName) {
1678 std::regex name_regex(R"(^control:([\w-]+)\/(\w+))");
1679 auto match = std::sregex_token_iterator(paramName.begin(), paramName.end(), name_regex, 0);
1680 if (match == std::sregex_token_iterator()) {
1681 throw runtime_error_f("Malformed process control spec: %s", paramName.c_str());
1682 }
1683 std::string task = std::sregex_token_iterator(paramName.begin(), paramName.end(), name_regex, 1)->str();
1684 std::string conf = std::sregex_token_iterator(paramName.begin(), paramName.end(), name_regex, 2)->str();
1685 return std::pair{task, conf};
1686 };
1687 bool altered = false;
1688 // verify uniformity of ccdb request for a each unique column
1689 std::unordered_map<std::string, std::string> ccdbPaths;
1690 for (auto& device : altered_workflow) {
1691 // ignore internal devices
1692 if (device.name.find("internal") != std::string::npos) {
1693 continue;
1694 }
1695 // ignore devices with no inputs
1696 if (device.inputs.empty() == true) {
1697 continue;
1698 }
1699 // ignore devices with no metadata in inputs
1700 auto hasMetadata = std::ranges::any_of(device.inputs, [](InputSpec const& spec) {
1701 return spec.metadata.empty() == false;
1702 });
1703 if (!hasMetadata) {
1704 continue;
1705 }
1706 // ignore devices with no control or ccdb options
1707 auto hasControlsOrCcdbs = std::ranges::any_of(device.inputs, [](InputSpec const& spec) {
1708 return std::ranges::any_of(spec.metadata, [](ConfigParamSpec const& param) {
1709 return param.type == VariantType::Bool && (param.name.starts_with("control:") || param.name.starts_with("ccdb:"));
1710 });
1711 });
1712 if (!hasControlsOrCcdbs) {
1713 continue;
1714 }
1715
1716 LOGP(debug, "Adjusting device {}", device.name.c_str());
1717
1718 auto configStore = DeviceConfigurationHelpers::getConfiguration(serviceRegistry, device.name.c_str(), device.options);
1719 if (configStore != nullptr) {
1720 auto reg = std::make_unique<ConfigParamRegistry>(std::move(configStore));
1721 for (auto& input : device.inputs) {
1722 for (auto& param : input.metadata) {
1723 if (param.type == VariantType::Bool && param.name.starts_with("control:")) {
1724 // update inputs from process switches
1725 if (param.name != "control:default" && param.name != "control:spawn" && param.name != "control:build" && param.name != "control:define") {
1726 auto confName = controlParamFromName(param.name).second;
1727 param.defaultValue = reg->get<bool>(confName.c_str());
1728 }
1729 } else if (param.type == VariantType::String && param.name.starts_with("ccdb:")) {
1730 // update inputs ccdb urls from configuration
1731 param.defaultValue = reg->get<std::string>(param.name.c_str());
1732 }
1733 }
1734 }
1735 }
1736 // after updating the urls, verify the consistency
1737 for (auto& input : device.inputs) {
1738 for (auto& param : input.metadata) {
1739 if (param.type != VariantType::String || !param.name.starts_with("ccdb:")) {
1740 continue;
1741 }
1742 if (ccdbPaths.contains(param.name)) {
1743 if (ccdbPaths[param.name] != param.defaultValue.get<std::string>()) {
1744 throw runtime_error_f(fmt::format("Task {} has a CCDB path inconsistent with another found in same workflow: {} vs {}",
1745 device.name.c_str(),
1746 param.defaultValue.get<std::string>().c_str(),
1747 ccdbPaths[param.name].c_str())
1748 .c_str());
1749 }
1750 } else {
1751 ccdbPaths.emplace(param.name, param.defaultValue.get<std::string>());
1752 }
1753 }
1754 }
1755
1757 LOGP(debug, "Original inputs: ");
1758 for (auto& input : device.inputs) {
1759 LOGP(debug, "-> {}", input.binding);
1760 }
1761 auto end = device.inputs.end();
1762 auto new_end = std::remove_if(device.inputs.begin(), device.inputs.end(), [](InputSpec& input) {
1763 auto requested = false;
1764 auto hasControls = false;
1765 for (auto& param : input.metadata) {
1766 if (param.type != VariantType::Bool) {
1767 continue;
1768 }
1769 if (param.name.find("control:") != std::string::npos) {
1770 hasControls = true;
1771 if (param.defaultValue.get<bool>() == true) {
1772 requested = true;
1773 break;
1774 }
1775 }
1776 }
1777 if (hasControls) {
1778 return !requested;
1779 }
1780 return false;
1781 });
1782 device.inputs.erase(new_end, end);
1783 LOGP(debug, "Adjusted inputs: ");
1784 for (auto& input : device.inputs) {
1785 LOGP(debug, "-> {}", input.binding);
1786 }
1787 altered = true;
1788 }
1789 WorkflowHelpers::adjustTopology(altered_workflow, *driverInfo.configContext);
1790 if (altered) {
1791 WorkflowSpecNode node{altered_workflow};
1792 for (auto& service : driverServices) {
1793 if (service.adjustTopology == nullptr) {
1794 continue;
1795 }
1796 service.adjustTopology(node, *driverInfo.configContext);
1797 }
1798 }
1799
1800 // These allow services customization via an environment variable
1801 OverrideServiceSpecs overrides = ServiceSpecHelpers::parseOverrides(getenv("DPL_OVERRIDE_SERVICES"));
1802 DeviceSpecHelpers::validate(altered_workflow);
1804 driverInfo.channelPolicies,
1805 driverInfo.completionPolicies,
1806 driverInfo.dispatchPolicies,
1807 driverInfo.resourcePolicies,
1808 driverInfo.callbacksPolicies,
1809 driverInfo.sendingPolicies,
1810 driverInfo.forwardingPolicies,
1811 runningWorkflow.devices,
1812 *resourceManager,
1813 driverInfo.uniqueWorkflowId,
1814 *driverInfo.configContext,
1815 !varmap["no-IPC"].as<bool>(),
1816 driverInfo.resourcesMonitoringInterval,
1817 varmap["channel-prefix"].as<std::string>(),
1818 overrides);
1819 metricProcessingCallbacks.clear();
1820 std::vector<std::string> matchingServices;
1821
1822 // FIXME: once moving to C++20, we can use templated lambdas.
1823 matchingServices.clear();
1824 for (auto& device : runningWorkflow.devices) {
1825 for (auto& service : device.services) {
1826 // If a service with the same name is already registered, skip it
1827 if (std::find(matchingServices.begin(), matchingServices.end(), service.name) != matchingServices.end()) {
1828 continue;
1829 }
1830 if (service.metricHandling) {
1831 metricProcessingCallbacks.push_back(service.metricHandling);
1832 matchingServices.push_back(service.name);
1833 }
1834 }
1835 }
1836
1837 // FIXME: once moving to C++20, we can use templated lambdas.
1838 matchingServices.clear();
1839 for (auto& device : runningWorkflow.devices) {
1840 for (auto& service : device.services) {
1841 // If a service with the same name is already registered, skip it
1842 if (std::find(matchingServices.begin(), matchingServices.end(), service.name) != matchingServices.end()) {
1843 continue;
1844 }
1845 if (service.summaryHandling) {
1846 summaryCallbacks.push_back(service.summaryHandling);
1847 matchingServices.push_back(service.name);
1848 }
1849 }
1850 }
1851
1852 preScheduleCallbacks.clear();
1853 matchingServices.clear();
1854 for (auto& device : runningWorkflow.devices) {
1855 for (auto& service : device.services) {
1856 // If a service with the same name is already registered, skip it
1857 if (std::find(matchingServices.begin(), matchingServices.end(), service.name) != matchingServices.end()) {
1858 continue;
1859 }
1860 if (service.preSchedule) {
1861 preScheduleCallbacks.push_back(service.preSchedule);
1862 }
1863 }
1864 }
1865 postScheduleCallbacks.clear();
1866 matchingServices.clear();
1867 for (auto& device : runningWorkflow.devices) {
1868 for (auto& service : device.services) {
1869 // If a service with the same name is already registered, skip it
1870 if (std::find(matchingServices.begin(), matchingServices.end(), service.name) != matchingServices.end()) {
1871 continue;
1872 }
1873 if (service.postSchedule) {
1874 postScheduleCallbacks.push_back(service.postSchedule);
1875 }
1876 }
1877 }
1878 driverInitCallbacks.clear();
1879 matchingServices.clear();
1880 for (auto& device : runningWorkflow.devices) {
1881 for (auto& service : device.services) {
1882 // If a service with the same name is already registered, skip it
1883 if (std::find(matchingServices.begin(), matchingServices.end(), service.name) != matchingServices.end()) {
1884 continue;
1885 }
1886 if (service.driverInit) {
1887 driverInitCallbacks.push_back(service.driverInit);
1888 }
1889 }
1890 }
1891
1892 // This should expand nodes so that we can build a consistent DAG.
1893
1894 // This updates the options in the runningWorkflow.devices
1895 for (auto& device : runningWorkflow.devices) {
1896 // ignore internal devices
1897 if (device.name.find("internal") != std::string::npos) {
1898 continue;
1899 }
1900 auto configStore = DeviceConfigurationHelpers::getConfiguration(serviceRegistry, device.name.c_str(), device.options);
1901 if (configStore != nullptr) {
1902 auto reg = std::make_unique<ConfigParamRegistry>(std::move(configStore));
1903 for (auto& option : device.options) {
1904 const char* name = option.name.c_str();
1905 switch (option.type) {
1906 case VariantType::Int:
1907 option.defaultValue = reg->get<int32_t>(name);
1908 break;
1909 case VariantType::Int8:
1910 option.defaultValue = reg->get<int8_t>(name);
1911 break;
1912 case VariantType::Int16:
1913 option.defaultValue = reg->get<int16_t>(name);
1914 break;
1915 case VariantType::UInt8:
1916 option.defaultValue = reg->get<uint8_t>(name);
1917 break;
1918 case VariantType::UInt16:
1919 option.defaultValue = reg->get<uint16_t>(name);
1920 break;
1921 case VariantType::UInt32:
1922 option.defaultValue = reg->get<uint32_t>(name);
1923 break;
1924 case VariantType::UInt64:
1925 option.defaultValue = reg->get<uint64_t>(name);
1926 break;
1927 case VariantType::Int64:
1928 option.defaultValue = reg->get<int64_t>(name);
1929 break;
1930 case VariantType::Float:
1931 option.defaultValue = reg->get<float>(name);
1932 break;
1933 case VariantType::Double:
1934 option.defaultValue = reg->get<double>(name);
1935 break;
1936 case VariantType::String:
1937 option.defaultValue = reg->get<std::string>(name);
1938 break;
1939 case VariantType::Bool:
1940 option.defaultValue = reg->get<bool>(name);
1941 break;
1942 case VariantType::ArrayInt:
1943 option.defaultValue = reg->get<std::vector<int>>(name);
1944 break;
1945 case VariantType::ArrayFloat:
1946 option.defaultValue = reg->get<std::vector<float>>(name);
1947 break;
1948 case VariantType::ArrayDouble:
1949 option.defaultValue = reg->get<std::vector<double>>(name);
1950 break;
1951 case VariantType::ArrayString:
1952 option.defaultValue = reg->get<std::vector<std::string>>(name);
1953 break;
1954 case VariantType::Array2DInt:
1955 option.defaultValue = reg->get<Array2D<int>>(name);
1956 break;
1957 case VariantType::Array2DFloat:
1958 option.defaultValue = reg->get<Array2D<float>>(name);
1959 break;
1960 case VariantType::Array2DDouble:
1961 option.defaultValue = reg->get<Array2D<double>>(name);
1962 break;
1963 case VariantType::LabeledArrayInt:
1964 option.defaultValue = reg->get<LabeledArray<int>>(name);
1965 break;
1966 case VariantType::LabeledArrayFloat:
1967 option.defaultValue = reg->get<LabeledArray<float>>(name);
1968 break;
1969 case VariantType::LabeledArrayDouble:
1970 option.defaultValue = reg->get<LabeledArray<double>>(name);
1971 break;
1972 case VariantType::LabeledArrayString:
1973 option.defaultValue = reg->get<LabeledArray<std::string>>(name);
1974 break;
1975 default:
1976 break;
1977 }
1978 }
1979 }
1980 }
1981 } catch (std::runtime_error& e) {
1982 LOGP(error, "invalid workflow in {}: {}", driverInfo.argv[0], e.what());
1983 return 1;
1986#ifdef DPL_ENABLE_BACKTRACE
1987 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
1988#endif
1989 LOGP(error, "invalid workflow in {}: {}", driverInfo.argv[0], err.what);
1990 return 1;
1991 } catch (...) {
1992 LOGP(error, "invalid workflow in {}: Unknown error while materialising workflow", driverInfo.argv[0]);
1993 return 1;
1994 }
1995 break;
1996 case DriverState::DO_CHILD:
1997 // We do not start the process if by default we are stopped.
1998 if (driverControl.defaultStopped) {
1999 kill(getpid(), SIGSTOP);
2000 }
2001 for (size_t di = 0; di < runningWorkflow.devices.size(); di++) {
2003 if (runningWorkflow.devices[di].id == frameworkId) {
2004 return doChild(driverInfo.argc, driverInfo.argv,
2005 serviceRegistry,
2006 driverInfo.configContext->services().get<DanglingEdgesContext>(),
2007 runningWorkflow, ref,
2008 driverConfig,
2009 driverInfo.processingPolicies,
2010 driverInfo.defaultDriverClient,
2011 loop);
2012 }
2013 }
2014 {
2015 std::ostringstream ss;
2016 for (auto& processor : workflow) {
2017 ss << " - " << processor.name << "\n";
2018 }
2019 for (auto& spec : runningWorkflow.devices) {
2020 ss << " - " << spec.name << "(" << spec.id << ")"
2021 << "\n";
2022 }
2023 driverInfo.lastError = fmt::format(
2024 "Unable to find component with id {}."
2025 " Available options:\n{}",
2026 frameworkId, ss.str());
2027 driverInfo.states.push_back(DriverState::QUIT_REQUESTED);
2028 }
2029 break;
2030 case DriverState::REDEPLOY_GUI:
2031 // The callback for the GUI needs to be recalculated every time
2032 // the deployed configuration changes, e.g. a new device
2033 // has been added to the topology.
2034 // We need to recreate the GUI callback every time we reschedule
2035 // because getGUIDebugger actually recreates the GUI state.
2036 // Notice also that we need the actual gui_timer only for the
2037 // case the GUI runs in interactive mode, however we deploy the
2038 // GUI in both interactive and non-interactive mode, if the
2039 // DPL_DRIVER_REMOTE_GUI environment variable is set.
2040 if (!driverConfig.batch || getenv("DPL_DRIVER_REMOTE_GUI")) {
2041 if (gui_timer) {
2042 uv_timer_stop(gui_timer);
2043 }
2044
2045 auto callback = debugGUI->getGUIDebugger(infos, runningWorkflow.devices, allStates, dataProcessorInfos, metricsInfos, driverInfo, controls, driverControl);
2046 guiContext.callback = [&serviceRegistry, &driverServices, &debugGUI, &infos, &runningWorkflow, &dataProcessorInfos, &metricsInfos, &driverInfo, &controls, &driverControl, callback]() {
2047 callback();
2048 for (auto& service : driverServices) {
2049 if (service.postRenderGUI) {
2050 service.postRenderGUI(serviceRegistry);
2051 }
2052 }
2053 };
2054 guiContext.window = window;
2055
2056 if (gui_timer) {
2057 gui_timer->data = &guiContext;
2058 uv_timer_start(gui_timer, gui_callback, 0, 20);
2059 }
2060 guiDeployedOnce = true;
2061 }
2062 break;
2063 case DriverState::MERGE_CONFIGS: {
2064 try {
2065 controls.resize(runningWorkflow.devices.size());
2068 if (varmap.count("dpl-tracing-flags")) {
2069 for (auto& control : controls) {
2070 auto tracingFlags = DeviceStateHelpers::parseTracingFlags(varmap["dpl-tracing-flags"].as<std::string>());
2071 control.tracingFlags = tracingFlags;
2072 }
2073 }
2074 deviceExecutions.resize(runningWorkflow.devices.size());
2075
2076 // Options which should be uniform across all
2077 // the subworkflow invokations.
2078 const auto uniformOptions = {
2079 "--aod-file",
2080 "--aod-memory-rate-limit",
2081 "--aod-writer-json",
2082 "--aod-writer-ntfmerge",
2083 "--aod-writer-resdir",
2084 "--aod-writer-resfile",
2085 "--aod-writer-resmode",
2086 "--aod-writer-maxfilesize",
2087 "--aod-writer-keep",
2088 "--aod-max-io-rate",
2089 "--aod-reader-backend",
2090 "--aod-reader-threads",
2091 "--aod-reader-prefetch",
2092 "--aod-reader-local-io",
2093 "--aod-reader-buffer-mb",
2094 "--aod-parent-access-level",
2095 "--aod-parent-base-path-replacement",
2096 "--aod-origin-level-mapping",
2097 "--driver-client-backend",
2098 "--fairmq-ipc-prefix",
2099 "--readers",
2100 "--ccdb-fetchers",
2101 "--resources-monitoring",
2102 "--resources-monitoring-file",
2103 "--resources-monitoring-dump-interval",
2104 "--time-limit",
2105 };
2106
2107 for (auto& option : uniformOptions) {
2108 DeviceSpecHelpers::reworkHomogeneousOption(dataProcessorInfos, option, nullptr);
2109 }
2110
2111 DeviceSpecHelpers::reworkShmSegmentSize(dataProcessorInfos);
2112 DeviceSpecHelpers::prepareArguments(driverControl.defaultQuiet,
2113 driverControl.defaultStopped,
2114 driverInfo.processingPolicies.termination == TerminationPolicy::WAIT,
2115 driverInfo.port,
2116 driverConfig,
2117 dataProcessorInfos,
2118 runningWorkflow.devices,
2119 deviceExecutions,
2120 controls,
2121 detectedParams,
2122 driverInfo.uniqueWorkflowId);
2125 LOGP(error, "unable to merge configurations in {}: {}", driverInfo.argv[0], err.what);
2126#ifdef DPL_ENABLE_BACKTRACE
2127 std::cerr << "\nStacktrace follows:\n\n";
2128 BacktraceHelpers::demangled_backtrace_symbols(err.backtrace, err.maxBacktrace, STDERR_FILENO);
2129#endif
2130 return 1;
2131 }
2132 } break;
2133 case DriverState::SCHEDULE: {
2134 // FIXME: for the moment modifying the topology means we rebuild completely
2135 // all the devices and we restart them. This is also what DDS does at
2136 // a larger scale. In principle one could try to do a delta and only
2137 // restart the data processors which need to be restarted.
2138 LOG(info) << "Redeployment of configuration asked.";
2139 std::ostringstream forwardedStdin;
2140 WorkflowSerializationHelpers::dump(forwardedStdin, workflow, dataProcessorInfos, commandInfo);
2141 infos.reserve(runningWorkflow.devices.size());
2142
2143 // This is guaranteed to be a single CPU.
2144 unsigned parentCPU = -1;
2145 unsigned parentNode = -1;
2146#if defined(__linux__) && __has_include(<sched.h>)
2147 parentCPU = sched_getcpu();
2148#elif __has_include(<linux/getcpu.h>)
2149 getcpu(&parentCPU, &parentNode, nullptr);
2150#elif __has_include(<cpuid.h>) && (__x86_64__ || __i386__)
2151 // FIXME: this is a last resort as it is apparently buggy
2152 // on some Intel CPUs.
2153 GETCPU(parentCPU);
2154#endif
2155 for (auto& callback : preScheduleCallbacks) {
2156 callback(serviceRegistry, {varmap});
2157 }
2158 childFds.resize(runningWorkflow.devices.size());
2159 for (int di = 0; di < (int)runningWorkflow.devices.size(); ++di) {
2160 auto& context = childFds[di];
2161 createPipes(context.childstdin);
2162 createPipes(context.childstdout);
2163 if (driverInfo.mode == DriverMode::EMBEDDED || runningWorkflow.devices[di].resource.hostname != driverInfo.deployHostname) {
2164 spawnRemoteDevice(loop, forwardedStdin.str(),
2165 runningWorkflow.devices[di], controls[di], deviceExecutions[di], infos, allStates);
2166 } else {
2167 DeviceRef ref{di};
2168 spawnDevice(loop,
2169 ref,
2170 runningWorkflow.devices, driverInfo,
2171 controls, deviceExecutions, infos,
2172 allStates,
2173 serviceRegistry, varmap,
2174 childFds, parentCPU, parentNode);
2175 }
2176 }
2177 handleSignals();
2178 handleChildrenStdio(&serverContext, forwardedStdin.str(), childFds, pollHandles);
2179 for (auto& callback : postScheduleCallbacks) {
2180 callback(serviceRegistry, {varmap});
2181 }
2182 assert(infos.empty() == false);
2183
2184 // In case resource monitoring is requested, we dump metrics to disk
2185 // every 3 minutes.
2186 if (driverInfo.resourcesMonitoringDumpInterval && ResourcesMonitoringHelper::isResourcesMonitoringEnabled(driverInfo.resourcesMonitoringInterval)) {
2187 uv_timer_init(loop, &metricDumpTimer);
2188 uv_timer_start(&metricDumpTimer, dumpMetricsCallback,
2189 driverInfo.resourcesMonitoringDumpInterval * 1000,
2190 driverInfo.resourcesMonitoringDumpInterval * 1000);
2191 }
2193 for (const auto& processorInfo : dataProcessorInfos) {
2194 const auto& cmdLineArgs = processorInfo.cmdLineArgs;
2195 if (std::find(cmdLineArgs.begin(), cmdLineArgs.end(), "--severity") != cmdLineArgs.end()) {
2196 for (size_t counter = 0; const auto& spec : runningWorkflow.devices) {
2197 if (spec.name.compare(processorInfo.name) == 0) {
2198 auto& info = infos[counter];
2199 const auto logLevelIt = std::find(cmdLineArgs.begin(), cmdLineArgs.end(), "--severity") + 1;
2200 if ((*logLevelIt).compare("debug") == 0) {
2201 info.logLevel = LogParsingHelpers::LogLevel::Debug;
2202 } else if ((*logLevelIt).compare("detail") == 0) {
2203 info.logLevel = LogParsingHelpers::LogLevel::Debug;
2204 } else if ((*logLevelIt).compare("info") == 0) {
2205 info.logLevel = LogParsingHelpers::LogLevel::Info;
2206 } else if ((*logLevelIt).compare("warning") == 0) {
2207 info.logLevel = LogParsingHelpers::LogLevel::Warning;
2208 } else if ((*logLevelIt).compare("error") == 0) {
2209 info.logLevel = LogParsingHelpers::LogLevel::Error;
2210 } else if ((*logLevelIt).compare("important") == 0) {
2211 info.logLevel = LogParsingHelpers::LogLevel::Info;
2212 } else if ((*logLevelIt).compare("alarm") == 0) {
2213 info.logLevel = LogParsingHelpers::LogLevel::Alarm;
2214 } else if ((*logLevelIt).compare("critical") == 0) {
2215 info.logLevel = LogParsingHelpers::LogLevel::Critical;
2216 } else if ((*logLevelIt).compare("fatal") == 0) {
2217 info.logLevel = LogParsingHelpers::LogLevel::Fatal;
2218 }
2219 break;
2220 }
2221 ++counter;
2222 }
2223 }
2224 }
2225 LOG(info) << "Redeployment of configuration done.";
2226 } break;
2227 case DriverState::RUNNING:
2228 // Run any pending libUV event loop, block if
2229 // any, so that we do not consume CPU time when the driver is
2230 // idle.
2231 devicesManager->flush();
2232 // We print the event loop for the gui only once every
2233 // 6000 iterations (i.e. ~2 minutes). To avoid spamming, while still
2234 // being able to see the event loop in case of a deadlock / systematic failure.
2235 if (guiTimerExpired == false) {
2236 O2_SIGNPOST_EVENT_EMIT(driver, sid, "mainloop", "Entering event loop with %{public}s", once ? "UV_RUN_ONCE" : "UV_RUN_NOWAIT");
2237 }
2238 uv_run(loop, once ? UV_RUN_ONCE : UV_RUN_NOWAIT);
2239 once = true;
2240 // Calculate what we should do next and eventually
2241 // show the GUI
2242 if (guiQuitRequested ||
2243 (driverInfo.processingPolicies.termination == TerminationPolicy::QUIT && (checkIfCanExit(infos) == true))) {
2244 // Something requested to quit. This can be a user
2245 // interaction with the GUI or (if --completion-policy=quit)
2246 // it could mean that the workflow does not have anything else to do.
2247 // Let's update the GUI one more time and then EXIT.
2248 LOG(info) << "Quitting";
2249 driverInfo.states.push_back(DriverState::QUIT_REQUESTED);
2250 } else if (infos.size() != runningWorkflow.devices.size()) {
2251 // If the number of devices is different from
2252 // the DeviceInfos it means the speicification
2253 // does not match what is running, so we need to do
2254 // further scheduling.
2255 driverInfo.states.push_back(DriverState::RUNNING);
2256 driverInfo.states.push_back(DriverState::REDEPLOY_GUI);
2257 driverInfo.states.push_back(DriverState::SCHEDULE);
2258 driverInfo.states.push_back(DriverState::MERGE_CONFIGS);
2259 } else if (runningWorkflow.devices.empty() && driverConfig.batch == true) {
2260 LOG(info) << "No device resulting from the workflow. Quitting.";
2261 // If there are no deviceSpecs, we exit.
2262 driverInfo.states.push_back(DriverState::EXIT);
2263 } else if (runningWorkflow.devices.empty() && driverConfig.batch == false && !guiDeployedOnce) {
2264 // In case of an empty workflow, we need to deploy the GUI at least once.
2265 driverInfo.states.push_back(DriverState::RUNNING);
2266 driverInfo.states.push_back(DriverState::REDEPLOY_GUI);
2267 } else {
2268 driverInfo.states.push_back(DriverState::RUNNING);
2269 }
2270 break;
2271 case DriverState::QUIT_REQUESTED: {
2272 std::time_t result = std::time(nullptr);
2273 char buffer[32];
2274 std::strncpy(buffer, std::ctime(&result), 26);
2275 O2_SIGNPOST_EVENT_EMIT_INFO(driver, sid, "mainloop", "Quit requested at %{public}s", buffer);
2276 guiQuitRequested = true;
2277 // We send SIGCONT to make sure stopped children are resumed
2278 killChildren(infos, SIGCONT);
2279 // We send SIGTERM to make sure we do the STOP transition in FairMQ
2280 killChildren(infos, SIGTERM);
2281 // We have a timer to send SIGUSR1 to make sure we advance all devices
2282 // in a timely manner.
2283 force_step_timer.data = &infos;
2284 uv_timer_start(&force_step_timer, single_step_callback, 0, 300);
2285 driverInfo.states.push_back(DriverState::HANDLE_CHILDREN);
2286 break;
2287 }
2288 case DriverState::HANDLE_CHILDREN: {
2289 // Run any pending libUV event loop, block if
2290 // any, so that we do not consume CPU time when the driver is
2291 // idle.
2292 uv_run(loop, once ? UV_RUN_ONCE : UV_RUN_NOWAIT);
2293 once = true;
2294 // I allow queueing of more sigchld only when
2295 // I process the previous call
2296 if (forceful_exit == true) {
2297 static bool forcefulExitMessage = true;
2298 if (forcefulExitMessage) {
2299 LOG(info) << "Forceful exit requested.";
2300 forcefulExitMessage = false;
2301 }
2302 killChildren(infos, SIGCONT);
2303 killChildren(infos, SIGKILL);
2304 }
2305 sigchld_requested = false;
2306 driverInfo.sigchldRequested = false;
2307 processChildrenOutput(loop, driverInfo, infos, runningWorkflow.devices, controls);
2308 hasError = processSigChild(infos, runningWorkflow.devices);
2309 allChildrenGone = areAllChildrenGone(infos);
2310 bool canExit = checkIfCanExit(infos);
2311 bool supposedToQuit = (guiQuitRequested || canExit || graceful_exit);
2312
2313 if (allChildrenGone && (supposedToQuit || driverInfo.processingPolicies.termination == TerminationPolicy::QUIT)) {
2314 // We move to the exit, regardless of where we were
2315 driverInfo.states.resize(0);
2316 driverInfo.states.push_back(DriverState::EXIT);
2317 } else if (hasError && driverInfo.processingPolicies.error == TerminationPolicy::QUIT && !supposedToQuit) {
2318 graceful_exit = 1;
2319 force_exit_timer.data = &infos;
2320 static bool forceful_timer_started = false;
2321 if (forceful_timer_started == false) {
2322 forceful_timer_started = true;
2323 uv_timer_start(&force_exit_timer, force_exit_callback, 15000, 3000);
2324 }
2325 driverInfo.states.push_back(DriverState::QUIT_REQUESTED);
2326 } else if (allChildrenGone == false && supposedToQuit) {
2327 driverInfo.states.push_back(DriverState::HANDLE_CHILDREN);
2328 } else {
2329 }
2330 } break;
2331 case DriverState::EXIT: {
2332 if (ResourcesMonitoringHelper::isResourcesMonitoringEnabled(driverInfo.resourcesMonitoringInterval)) {
2333 if (driverInfo.resourcesMonitoringDumpInterval) {
2334 uv_timer_stop(&metricDumpTimer);
2335 }
2336 LOGP(info, "Dumping performance metrics to {}.json file", driverInfo.resourcesMonitoringFilename);
2337 dumpMetricsCallback(&metricDumpTimer);
2338 }
2339 dumpRunSummary(serverContext, driverInfo, infos, runningWorkflow.devices);
2340 // This is a clean exit. Before we do so, if required,
2341 // we dump the configuration of all the devices so that
2342 // we can reuse it. Notice we do not dump anything if
2343 // the workflow was not really run.
2344 // NOTE: is this really what we want? should we run
2345 // SCHEDULE and dump the full configuration as well?
2346 if (infos.empty()) {
2347 return 0;
2348 }
2349 boost::property_tree::ptree finalConfig;
2350 assert(infos.size() == runningWorkflow.devices.size());
2351 for (size_t di = 0; di < infos.size(); ++di) {
2352 auto& info = infos[di];
2353 auto& spec = runningWorkflow.devices[di];
2354 finalConfig.put_child(spec.name, info.currentConfig);
2355 }
2356 LOG(info) << "Dumping used configuration in dpl-config.json";
2357
2358 std::ofstream outDPLConfigFile("dpl-config.json", std::ios::out);
2359 if (outDPLConfigFile.is_open()) {
2360 boost::property_tree::write_json(outDPLConfigFile, finalConfig);
2361 } else {
2362 LOGP(warning, "Could not write out final configuration file. Read only run folder?");
2363 }
2364 if (driverInfo.noSHMCleanup) {
2365 LOGP(warning, "Not cleaning up shared memory.");
2366 } else {
2367 cleanupSHM(driverInfo.uniqueWorkflowId);
2368 }
2369 return calculateExitCode(driverInfo, runningWorkflow.devices, infos);
2370 }
2371 case DriverState::PERFORM_CALLBACKS:
2372 for (auto& callback : driverControl.callbacks) {
2373 callback(workflow, runningWorkflow.devices, deviceExecutions, dataProcessorInfos, commandInfo);
2374 }
2375 driverControl.callbacks.clear();
2376 break;
2377 default:
2378 LOG(error) << "Driver transitioned in an unknown state("
2379 << "current: " << (int)current
2380 << ", previous: " << (int)previous
2381 << "). Shutting down.";
2382 driverInfo.states.push_back(DriverState::QUIT_REQUESTED);
2383 }
2384 }
2385 O2_SIGNPOST_END(driver, sid, "driver", "End driver loop");
2386}
2387
2388// Print help
2389void printHelp(bpo::variables_map const& varmap,
2390 bpo::options_description const& executorOptions,
2391 std::vector<DataProcessorSpec> const& physicalWorkflow,
2392 std::vector<ConfigParamSpec> const& currentWorkflowOptions)
2393{
2394 auto mode = varmap["help"].as<std::string>();
2395 bpo::options_description helpOptions;
2396 if (mode == "full" || mode == "short" || mode == "executor") {
2397 helpOptions.add(executorOptions);
2398 }
2399 // this time no veto is applied, so all the options are added for printout
2400 if (mode == "executor") {
2401 // nothing more
2402 } else if (mode == "workflow") {
2403 // executor options and workflow options, skip the actual workflow
2404 o2::framework::WorkflowSpec emptyWorkflow;
2405 helpOptions.add(ConfigParamsHelper::prepareOptionDescriptions(emptyWorkflow, currentWorkflowOptions));
2406 } else if (mode == "full" || mode == "short") {
2407 helpOptions.add(ConfigParamsHelper::prepareOptionDescriptions(physicalWorkflow, currentWorkflowOptions,
2408 bpo::options_description(),
2409 mode));
2410 } else {
2411 helpOptions.add(ConfigParamsHelper::prepareOptionDescriptions(physicalWorkflow, {},
2412 bpo::options_description(),
2413 mode));
2414 }
2415 if (helpOptions.options().size() == 0) {
2416 // the specified argument is invalid, add at leat the executor options
2417 mode += " is an invalid argument, please use correct argument for";
2418 helpOptions.add(executorOptions);
2419 }
2420 std::cout << "ALICE O2 DPL workflow driver" //
2421 << " (" << mode << " help)" << std::endl //
2422 << helpOptions << std::endl; //
2423}
2424
2425// Helper to find out if stdout is actually attached to a pipe.
2427{
2428 struct stat s;
2429 fstat(STDOUT_FILENO, &s);
2430 return ((s.st_mode & S_IFIFO) != 0);
2431}
2432
2434{
2435 struct stat s;
2436 int r = fstat(STDIN_FILENO, &s);
2437 // If stdin cannot be statted, we assume the shell is some sort of
2438 // non-interactive container thing
2439 if (r < 0) {
2440 return false;
2441 }
2442 // If stdin is a pipe or a file, we try to fetch configuration from there
2443 return ((s.st_mode & S_IFIFO) != 0 || (s.st_mode & S_IFREG) != 0);
2444}
2445
2447{
2448 struct CloningSpec {
2449 std::string templateMatcher;
2450 std::string cloneName;
2451 };
2452 auto s = ctx.options().get<std::string>("clone");
2453 std::vector<CloningSpec> specs;
2454 std::string delimiter = ",";
2455
2456 while (s.empty() == false) {
2457 auto newPos = s.find(delimiter);
2458 auto token = s.substr(0, newPos);
2459 auto split = token.find(":");
2460 if (split == std::string::npos) {
2461 throw std::runtime_error("bad clone definition. Syntax <template-processor>:<clone-name>");
2462 }
2463 auto key = token.substr(0, split);
2464 token.erase(0, split + 1);
2465 size_t error;
2466 std::string value = "";
2467 try {
2468 auto numValue = std::stoll(token, &error, 10);
2469 if (token[error] != '\0') {
2470 throw std::runtime_error("bad name for clone:" + token);
2471 }
2472 value = key + "_c" + std::to_string(numValue);
2473 } catch (std::invalid_argument& e) {
2474 value = token;
2475 }
2476 specs.push_back({key, value});
2477 s.erase(0, newPos + (newPos == std::string::npos ? 0 : 1));
2478 }
2479 if (s.empty() == false && specs.empty() == true) {
2480 throw std::runtime_error("bad pipeline definition. Syntax <processor>:<pipeline>");
2481 }
2482
2483 std::vector<DataProcessorSpec> extraSpecs;
2484 for (auto& spec : specs) {
2485 for (auto& processor : workflow) {
2486 if (processor.name == spec.templateMatcher) {
2487 auto clone = processor;
2488 clone.name = spec.cloneName;
2489 extraSpecs.push_back(clone);
2490 }
2491 }
2492 }
2493 workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end());
2494}
2495
2497{
2498 struct PipelineSpec {
2499 std::string matcher;
2500 int64_t pipeline;
2501 };
2502 auto s = ctx.options().get<std::string>("pipeline");
2503 std::vector<PipelineSpec> specs;
2504 std::string delimiter = ",";
2505
2506 while (s.empty() == false) {
2507 auto newPos = s.find(delimiter);
2508 auto token = s.substr(0, newPos);
2509 auto split = token.find(":");
2510 if (split == std::string::npos) {
2511 throw std::runtime_error("bad pipeline definition. Syntax <processor>:<pipeline>");
2512 }
2513 auto key = token.substr(0, split);
2514 token.erase(0, split + 1);
2515 size_t error;
2516 auto value = std::stoll(token, &error, 10);
2517 if (token[error] != '\0') {
2518 throw std::runtime_error("Bad pipeline definition. Expecting integer");
2519 }
2520 specs.push_back({key, value});
2521 s.erase(0, newPos + (newPos == std::string::npos ? 0 : 1));
2522 }
2523 if (s.empty() == false && specs.empty() == true) {
2524 throw std::runtime_error("bad pipeline definition. Syntax <processor>:<pipeline>");
2525 }
2526
2527 for (auto& spec : specs) {
2528 for (auto& processor : workflow) {
2529 if (processor.name == spec.matcher) {
2530 processor.maxInputTimeslices = spec.pipeline;
2531 }
2532 }
2533 }
2534}
2535
2537{
2538 struct LabelsSpec {
2539 std::string_view matcher;
2540 std::vector<std::string> labels;
2541 };
2542 std::vector<LabelsSpec> specs;
2543
2544 auto labelsString = ctx.options().get<std::string>("labels");
2545 if (labelsString.empty()) {
2546 return;
2547 }
2548 std::string_view sv{labelsString};
2549
2550 size_t specStart = 0;
2551 size_t specEnd = 0;
2552 constexpr char specDelim = ',';
2553 constexpr char labelDelim = ':';
2554 do {
2555 specEnd = sv.find(specDelim, specStart);
2556 auto token = sv.substr(specStart, specEnd == std::string_view::npos ? std::string_view::npos : specEnd - specStart);
2557 if (token.empty()) {
2558 throw std::runtime_error("bad labels definition. Syntax <processor>:<label>[:<label>][,<processor>:<label>[:<label>]");
2559 }
2560
2561 size_t labelDelimPos = token.find(labelDelim);
2562 if (labelDelimPos == 0 || labelDelimPos == std::string_view::npos) {
2563 throw std::runtime_error("bad labels definition. Syntax <processor>:<label>[:<label>][,<processor>:<label>[:<label>]");
2564 }
2565 LabelsSpec spec{.matcher = token.substr(0, labelDelimPos), .labels = {}};
2566
2567 size_t labelEnd = labelDelimPos + 1;
2568 do {
2569 size_t labelStart = labelDelimPos + 1;
2570 labelEnd = token.find(labelDelim, labelStart);
2571 auto label = labelEnd == std::string_view::npos ? token.substr(labelStart) : token.substr(labelStart, labelEnd - labelStart);
2572 if (label.empty()) {
2573 throw std::runtime_error("bad labels definition. Syntax <processor>:<label>[:<label>][,<processor>:<label>[:<label>]");
2574 }
2575 spec.labels.emplace_back(label);
2576 labelDelimPos = labelEnd;
2577 } while (labelEnd != std::string_view::npos);
2578
2579 specs.push_back(spec);
2580 specStart = specEnd + 1;
2581 } while (specEnd != std::string_view::npos);
2582
2583 if (labelsString.empty() == false && specs.empty() == true) {
2584 throw std::runtime_error("bad labels definition. Syntax <processor>:<label>[:<label>][,<processor>:<label>[:<label>]");
2585 }
2586
2587 for (auto& spec : specs) {
2588 for (auto& processor : workflow) {
2589 if (processor.name == spec.matcher) {
2590 for (const auto& label : spec.labels) {
2591 if (std::find_if(processor.labels.begin(), processor.labels.end(),
2592 [label](const auto& procLabel) { return procLabel.value == label; }) == processor.labels.end()) {
2593 processor.labels.push_back({label});
2594 }
2595 }
2596 }
2597 }
2598 }
2599}
2600
2602void initialiseDriverControl(bpo::variables_map const& varmap,
2603 DriverInfo& driverInfo,
2604 DriverControl& control)
2605{
2606 // Control is initialised outside the main loop because
2607 // command line options are really affecting control.
2608 control.defaultQuiet = varmap["quiet"].as<bool>();
2609 control.defaultStopped = varmap["stop"].as<bool>();
2610
2611 if (varmap["single-step"].as<bool>()) {
2612 control.state = DriverControlState::STEP;
2613 } else {
2614 control.state = DriverControlState::PLAY;
2615 }
2616
2617 if (varmap["graphviz"].as<bool>()) {
2618 // Dump a graphviz representation of what I will do.
2619 control.callbacks = {[](WorkflowSpec const&,
2620 DeviceSpecs const& specs,
2621 DeviceExecutions const&,
2623 CommandInfo const&) {
2625 }};
2626 control.forcedTransitions = {
2627 DriverState::EXIT, //
2628 DriverState::PERFORM_CALLBACKS, //
2629 DriverState::MERGE_CONFIGS, //
2630 DriverState::IMPORT_CURRENT_WORKFLOW, //
2631 DriverState::MATERIALISE_WORKFLOW //
2632 };
2633 } else if (!varmap["dds"].as<std::string>().empty()) {
2634 // Dump a DDS representation of what I will do.
2635 // Notice that compared to DDS we need to schedule things,
2636 // because DDS needs to be able to have actual Executions in
2637 // order to provide a correct configuration.
2638 control.callbacks = {[filename = varmap["dds"].as<std::string>(),
2639 workflowSuffix = varmap["dds-workflow-suffix"],
2640 driverMode = driverInfo.mode](WorkflowSpec const& workflow,
2641 DeviceSpecs const& specs,
2642 DeviceExecutions const& executions,
2643 DataProcessorInfos& dataProcessorInfos,
2644 CommandInfo const& commandInfo) {
2645 if (filename == "-") {
2646 DDSConfigHelpers::dumpDeviceSpec2DDS(std::cout, driverMode, workflowSuffix.as<std::string>(), workflow, dataProcessorInfos, specs, executions, commandInfo);
2647 } else {
2648 std::ofstream out(filename);
2649 DDSConfigHelpers::dumpDeviceSpec2DDS(out, driverMode, workflowSuffix.as<std::string>(), workflow, dataProcessorInfos, specs, executions, commandInfo);
2650 }
2651 }};
2652 control.forcedTransitions = {
2653 DriverState::EXIT, //
2654 DriverState::PERFORM_CALLBACKS, //
2655 DriverState::MERGE_CONFIGS, //
2656 DriverState::IMPORT_CURRENT_WORKFLOW, //
2657 DriverState::MATERIALISE_WORKFLOW //
2658 };
2659 } else if (!varmap["o2-control"].as<std::string>().empty() or !varmap["mermaid"].as<std::string>().empty()) {
2660 // Dump the workflow in o2-control and/or mermaid format
2661 control.callbacks = {[filename = varmap["mermaid"].as<std::string>(),
2662 workflowName = varmap["o2-control"].as<std::string>()](WorkflowSpec const&,
2663 DeviceSpecs const& specs,
2664 DeviceExecutions const& executions,
2666 CommandInfo const& commandInfo) {
2667 if (!workflowName.empty()) {
2668 dumpDeviceSpec2O2Control(workflowName, specs, executions, commandInfo);
2669 }
2670 if (!filename.empty()) {
2671 if (filename == "-") {
2673 } else {
2674 std::ofstream output(filename);
2676 }
2677 }
2678 }};
2679 control.forcedTransitions = {
2680 DriverState::EXIT, //
2681 DriverState::PERFORM_CALLBACKS, //
2682 DriverState::MERGE_CONFIGS, //
2683 DriverState::IMPORT_CURRENT_WORKFLOW, //
2684 DriverState::MATERIALISE_WORKFLOW //
2685 };
2686
2687 } else if (varmap.count("id")) {
2688 // Add our own stacktrace dumping
2689 if (getenv("O2_NO_CATCHALL_EXCEPTIONS") != nullptr && strcmp(getenv("O2_NO_CATCHALL_EXCEPTIONS"), "0") != 0) {
2690 LOGP(info, "Not instrumenting crash signals because O2_NO_CATCHALL_EXCEPTIONS is set");
2691 gEnv->SetValue("Root.Stacktrace", "no");
2692 gSystem->ResetSignal(kSigSegmentationViolation, kTRUE);
2693 rlimit limit;
2694 if (getrlimit(RLIMIT_CORE, &limit) == 0) {
2695 LOGP(info, "Core limit: {} {}", limit.rlim_cur, limit.rlim_max);
2696 }
2697 }
2698 if (varmap["stacktrace-on-signal"].as<std::string>() == "simple" && (getenv("O2_NO_CATCHALL_EXCEPTIONS") == nullptr || strcmp(getenv("O2_NO_CATCHALL_EXCEPTIONS"), "0") == 0)) {
2699 LOGP(info, "Instrumenting crash signals");
2700 signal(SIGSEGV, handle_crash);
2701 signal(SIGABRT, handle_crash);
2702 signal(SIGBUS, handle_crash);
2703 signal(SIGILL, handle_crash);
2704 signal(SIGFPE, handle_crash);
2705 }
2706 // FIXME: for the time being each child needs to recalculate the workflow,
2707 // so that it can understand what it needs to do. This is obviously
2708 // a bad idea. In the future we should have the client be pushed
2709 // it's own configuration by the driver.
2710 control.forcedTransitions = {
2711 DriverState::DO_CHILD, //
2712 DriverState::BIND_GUI_PORT, //
2713 DriverState::MERGE_CONFIGS, //
2714 DriverState::IMPORT_CURRENT_WORKFLOW, //
2715 DriverState::MATERIALISE_WORKFLOW //
2716 };
2717 } else if ((varmap["dump-workflow"].as<bool>() == true) || (varmap["run"].as<bool>() == false && varmap.count("id") == 0 && isOutputToPipe())) {
2718 control.callbacks = {[filename = varmap["dump-workflow-file"].as<std::string>()](WorkflowSpec const& workflow,
2719 DeviceSpecs const&,
2720 DeviceExecutions const&,
2721 DataProcessorInfos& dataProcessorInfos,
2722 CommandInfo const& commandInfo) {
2723 if (filename == "-") {
2724 WorkflowSerializationHelpers::dump(std::cout, workflow, dataProcessorInfos, commandInfo);
2725 // FIXME: this is to avoid trailing garbage..
2726 exit(0);
2727 } else {
2728 std::ofstream output(filename);
2729 WorkflowSerializationHelpers::dump(output, workflow, dataProcessorInfos, commandInfo);
2730 }
2731 }};
2732 control.forcedTransitions = {
2733 DriverState::EXIT, //
2734 DriverState::PERFORM_CALLBACKS, //
2735 DriverState::MERGE_CONFIGS, //
2736 DriverState::IMPORT_CURRENT_WORKFLOW, //
2737 DriverState::MATERIALISE_WORKFLOW //
2738 };
2739 } else {
2740 // By default we simply start the main loop of the driver.
2741 control.forcedTransitions = {
2742 DriverState::INIT, //
2743 DriverState::BIND_GUI_PORT, //
2744 DriverState::IMPORT_CURRENT_WORKFLOW, //
2745 DriverState::MATERIALISE_WORKFLOW //
2746 };
2747 }
2748}
2749
2751void conflicting_options(const boost::program_options::variables_map& vm,
2752 const std::string& opt1, const std::string& opt2)
2753{
2754 if (vm.count(opt1) && !vm[opt1].defaulted() &&
2755 vm.count(opt2) && !vm[opt2].defaulted()) {
2756 throw std::logic_error(std::string("Conflicting options '") +
2757 opt1 + "' and '" + opt2 + "'.");
2758 }
2759}
2760
2761template <typename T>
2763 std::vector<T>& v,
2764 std::vector<int>& indices)
2765{
2766 using std::swap; // to permit Koenig lookup
2767 for (int i = 0; i < (int)indices.size(); i++) {
2768 auto current = i;
2769 while (i != indices[current]) {
2770 auto next = indices[current];
2771 swap(v[current], v[next]);
2772 indices[current] = current;
2773 current = next;
2774 }
2775 indices[current] = current;
2776 }
2777}
2778
2779// Check if the workflow is resiliant to failures
2780void checkNonResiliency(std::vector<DataProcessorSpec> const& specs,
2781 std::vector<std::pair<int, int>> const& edges)
2782{
2783 auto checkExpendable = [](DataProcessorLabel const& label) {
2784 return label.value == "expendable";
2785 };
2786 auto checkResilient = [](DataProcessorLabel const& label) {
2787 return label.value == "resilient" || label.value == "expendable";
2788 };
2789
2790 for (auto& edge : edges) {
2791 auto& src = specs[edge.first];
2792 auto& dst = specs[edge.second];
2793 if (std::none_of(src.labels.begin(), src.labels.end(), checkExpendable)) {
2794 continue;
2795 }
2796 if (std::any_of(dst.labels.begin(), dst.labels.end(), checkResilient)) {
2797 continue;
2798 }
2799 throw std::runtime_error("Workflow is not resiliant to failures. Processor " + dst.name + " gets inputs from expendable devices, but is not marked as expendable or resilient itself.");
2800 }
2801}
2802
2803std::string debugTopoInfo(std::vector<DataProcessorSpec> const& specs,
2804 std::vector<TopoIndexInfo> const& infos,
2805 std::vector<std::pair<int, int>> const& edges)
2806{
2807 std::ostringstream out;
2808
2809 out << "\nTopological info:\n";
2810 for (auto& ti : infos) {
2811 out << specs[ti.index].name << " (index: " << ti.index << ", layer: " << ti.layer << ")\n";
2812 out << " Inputs:\n";
2813 for (auto& ii : specs[ti.index].inputs) {
2814 out << " - " << DataSpecUtils::describe(ii) << "\n";
2815 }
2816 out << "\n Outputs:\n";
2817 for (auto& ii : specs[ti.index].outputs) {
2818 out << " - " << DataSpecUtils::describe(ii) << "\n";
2819 }
2820 }
2821 out << "\nEdges values:\n";
2822 for (auto& e : edges) {
2823 out << specs[e.second].name << " depends on " << specs[e.first].name << "\n";
2824 }
2825 for (auto& d : specs) {
2826 out << "- " << d.name << std::endl;
2827 }
2829 return out.str();
2830}
2831
2832void enableSignposts(std::string const& signpostsToEnable)
2833{
2834 static pid_t pid = getpid();
2835 if (signpostsToEnable.empty() == true) {
2836 auto printAllSignposts = [](char const* name, void* l, void* context) {
2837 auto* log = (_o2_log_t*)l;
2838 LOGP(detail, "Signpost stream {} disabled. Enable it with o2-log -p {} -a {}", name, pid, (void*)&log->stacktrace);
2839 return true;
2840 };
2841 o2_walk_logs(printAllSignposts, nullptr);
2842 return;
2843 }
2844 auto matchingLogEnabler = [](char const* name, void* l, void* context) {
2845 auto* log = (_o2_log_t*)l;
2846 auto* selectedName = (char const*)context;
2847 std::string prefix = "ch.cern.aliceo2.";
2848 auto* last = strchr(selectedName, ':');
2849 int maxDepth = 1;
2850 if (last) {
2851 char* err;
2852 maxDepth = strtol(last + 1, &err, 10);
2853 if (*(last + 1) == '\0' || *err != '\0') {
2854 maxDepth = 1;
2855 }
2856 }
2857
2858 auto fullName = prefix + std::string{selectedName, last ? last - selectedName : strlen(selectedName)};
2859 if (fullName == name) {
2860 LOGP(info, "Enabling signposts for stream \"{}\" with depth {}.", fullName, maxDepth);
2862 return false;
2863 } else {
2864 LOGP(info, "Signpost stream \"{}\" disabled. Enable it with o2-log -p {} -a {}", name, pid, (void*)&log->stacktrace);
2865 }
2866 return true;
2867 };
2868 // Split signpostsToEnable by comma using strtok_r
2869 char* saveptr;
2870 char* src = const_cast<char*>(signpostsToEnable.data());
2871 auto* token = strtok_r(src, ",", &saveptr);
2872 while (token) {
2873 o2_walk_logs(matchingLogEnabler, token);
2874 token = strtok_r(nullptr, ",", &saveptr);
2875 }
2876}
2877
2878void overrideAll(o2::framework::ConfigContext& ctx, std::vector<o2::framework::DataProcessorSpec>& workflow)
2879{
2880 overrideCloning(ctx, workflow);
2881 overridePipeline(ctx, workflow);
2882 overrideLabels(ctx, workflow);
2883}
2884
2885o2::framework::ConfigContext createConfigContext(std::unique_ptr<ConfigParamRegistry>& workflowOptionsRegistry,
2886 o2::framework::ServiceRegistry& configRegistry,
2887 std::vector<o2::framework::ConfigParamSpec>& workflowOptions,
2888 std::vector<o2::framework::ConfigParamSpec>& extraOptions, int argc, char** argv)
2889{
2890 std::vector<std::unique_ptr<o2::framework::ParamRetriever>> retrievers;
2891 std::unique_ptr<o2::framework::ParamRetriever> retriever{new o2::framework::BoostOptionsRetriever(true, argc, argv)};
2892 retrievers.emplace_back(std::move(retriever));
2893 auto workflowOptionsStore = std::make_unique<o2::framework::ConfigParamStore>(workflowOptions, std::move(retrievers));
2894 workflowOptionsStore->preload();
2895 workflowOptionsStore->activate();
2896 workflowOptionsRegistry = std::make_unique<ConfigParamRegistry>(std::move(workflowOptionsStore));
2897 extraOptions = o2::framework::ConfigParamDiscovery::discover(*workflowOptionsRegistry, argc, argv);
2898 for (auto& extra : extraOptions) {
2899 workflowOptions.push_back(extra);
2900 }
2901
2902 return o2::framework::ConfigContext(*workflowOptionsRegistry, o2::framework::ServiceRegistryRef{configRegistry}, argc, argv);
2903}
2904
2905std::unique_ptr<o2::framework::ServiceRegistry> createRegistry()
2906{
2907 return std::make_unique<o2::framework::ServiceRegistry>();
2908}
2909
2910// This is a toy executor for the workflow spec
2911// What it needs to do is:
2912//
2913// - Print the properties of each DataProcessorSpec
2914// - Fork one process per DataProcessorSpec
2915// - Parent -> wait for all the children to complete (eventually
2916// killing them all on ctrl-c).
2917// - Child, pick the data-processor ID and start a O2DataProcessorDevice for
2918// each DataProcessorSpec
2919int doMain(int argc, char** argv, o2::framework::WorkflowSpec const& workflow,
2920 std::vector<ChannelConfigurationPolicy> const& channelPolicies,
2921 std::vector<CompletionPolicy> const& completionPolicies,
2922 std::vector<DispatchPolicy> const& dispatchPolicies,
2923 std::vector<ResourcePolicy> const& resourcePolicies,
2924 std::vector<CallbacksPolicy> const& callbacksPolicies,
2925 std::vector<SendingPolicy> const& sendingPolicies,
2926 std::vector<ConfigParamSpec> const& currentWorkflowOptions,
2927 std::vector<ConfigParamSpec> const& detectedParams,
2928 o2::framework::ConfigContext& configContext)
2929{
2930 // Peek very early in the driver options and look for
2931 // signposts, so the we can enable it without going through the whole dance
2932 if (getenv("DPL_DRIVER_SIGNPOSTS")) {
2933 enableSignposts(getenv("DPL_DRIVER_SIGNPOSTS"));
2934 }
2935
2936 std::vector<std::string> currentArgs;
2937 std::vector<PluginInfo> plugins;
2938 std::vector<ForwardingPolicy> forwardingPolicies = ForwardingPolicy::createDefaultPolicies();
2939
2940 for (int ai = 1; ai < argc; ++ai) {
2941 currentArgs.emplace_back(argv[ai]);
2942 }
2943
2944 WorkflowInfo currentWorkflow{
2945 argv[0],
2946 currentArgs,
2947 currentWorkflowOptions};
2948
2949 ProcessingPolicies processingPolicies;
2950 enum LogParsingHelpers::LogLevel minFailureLevel;
2951 bpo::options_description executorOptions("Executor options");
2952 const char* helpDescription = "print help: short, full, executor, or processor name";
2953 enum DriverMode driverMode;
2954 executorOptions.add_options() //
2955 ("help,h", bpo::value<std::string>()->implicit_value("short"), helpDescription) // //
2956 ("quiet,q", bpo::value<bool>()->zero_tokens()->default_value(false), "quiet operation") // //
2957 ("stop,s", bpo::value<bool>()->zero_tokens()->default_value(false), "stop before device start") // //
2958 ("single-step", bpo::value<bool>()->zero_tokens()->default_value(false), "start in single step mode") // //
2959 ("batch,b", bpo::value<std::vector<std::string>>()->zero_tokens()->composing(), "batch processing mode") // //
2960 ("no-batch", bpo::value<bool>()->zero_tokens(), "force gui processing mode") // //
2961 ("no-cleanup", bpo::value<bool>()->zero_tokens()->default_value(false), "do not cleanup the shm segment") // //
2962 ("hostname", bpo::value<std::string>()->default_value("localhost"), "hostname to deploy") // //
2963 ("resources", bpo::value<std::string>()->default_value(""), "resources allocated for the workflow") // //
2964 ("start-port,p", bpo::value<unsigned short>()->default_value(22000), "start port to allocate") // //
2965 ("port-range,pr", bpo::value<unsigned short>()->default_value(1000), "ports in range") // //
2966 ("completion-policy,c", bpo::value<TerminationPolicy>(&processingPolicies.termination)->default_value(TerminationPolicy::QUIT), // //
2967 "what to do when processing is finished: quit, wait") // //
2968 ("error-policy", bpo::value<TerminationPolicy>(&processingPolicies.error)->default_value(TerminationPolicy::QUIT), // //
2969 "what to do when a device has an error: quit, wait") // //
2970 ("min-failure-level", bpo::value<LogParsingHelpers::LogLevel>(&minFailureLevel)->default_value(LogParsingHelpers::LogLevel::Fatal), // //
2971 "minimum message level which will be considered as fatal and exit with 1") // //
2972 ("graphviz,g", bpo::value<bool>()->zero_tokens()->default_value(false), "produce graphviz output") // //
2973 ("mermaid", bpo::value<std::string>()->default_value(""), "produce graph output in mermaid format in file under specified name or on stdout if argument is \"-\"") // //
2974 ("timeout,t", bpo::value<uint64_t>()->default_value(0), "forced exit timeout (in seconds)") // //
2975 ("dds,D", bpo::value<std::string>()->default_value(""), "create DDS configuration") // //
2976 ("dds-workflow-suffix,D", bpo::value<std::string>()->default_value(""), "suffix for DDS names") // //
2977 ("dump-workflow,dump", bpo::value<bool>()->zero_tokens()->default_value(false), "dump workflow as JSON") // //
2978 ("dump-workflow-file", bpo::value<std::string>()->default_value("-"), "file to which do the dump") // //
2979 ("driver-mode", bpo::value<DriverMode>(&driverMode)->default_value(DriverMode::STANDALONE), R"(how to run the driver. default: "standalone". Valid: "embedded")") // //
2980 ("run", bpo::value<bool>()->zero_tokens()->default_value(false), "run workflow merged so far. It implies --batch. Use --no-batch to see the GUI") // //
2981 ("no-IPC", bpo::value<bool>()->zero_tokens()->default_value(false), "disable IPC topology optimization") // //
2982 ("o2-control,o2", bpo::value<std::string>()->default_value(""), "dump O2 Control workflow configuration under the specified name") //
2983 ("resources-monitoring", bpo::value<unsigned short>()->default_value(0), "enable cpu/memory monitoring for provided interval in seconds") //
2984 ("resources-monitoring-file", bpo::value<std::string>()->default_value("performanceMetrics.json"), "file where to dump the metrics") //
2985 ("resources-monitoring-dump-interval", bpo::value<unsigned short>()->default_value(0), "dump monitoring information to disk every provided seconds"); //
2986 // some of the options must be forwarded by default to the device
2987 executorOptions.add(DeviceSpecHelpers::getForwardedDeviceOptions());
2988
2989 gHiddenDeviceOptions.add_options() //
2990 ("id,i", bpo::value<std::string>(), "device id for child spawning") //
2991 ("channel-config", bpo::value<std::vector<std::string>>(), "channel configuration") //
2992 ("control", "control plugin") //
2993 ("log-color", "logging color scheme")("color", "logging color scheme");
2994
2995 bpo::options_description visibleOptions;
2996 visibleOptions.add(executorOptions);
2997
2998 auto physicalWorkflow = workflow;
2999 std::map<std::string, size_t> rankIndex;
3000 // We remove the duplicates because for the moment child get themself twice:
3001 // once from the actual definition in the child, a second time from the
3002 // configuration they get passed by their parents.
3003 // Notice that we do not know in which order we will get the workflows, so
3004 // while we keep the order of DataProcessors we reshuffle them based on
3005 // some hopefully unique hash.
3006 size_t workflowHashA = 0;
3007 std::hash<std::string> hash_fn;
3008
3009 for (auto& dp : workflow) {
3010 workflowHashA += hash_fn(dp.name);
3011 }
3012
3013 for (auto& dp : workflow) {
3014 rankIndex.insert(std::make_pair(dp.name, workflowHashA));
3015 }
3016
3017 std::vector<DataProcessorInfo> dataProcessorInfos;
3018 CommandInfo commandInfo{};
3019
3020 if (isatty(STDIN_FILENO) == false && isInputConfig()) {
3021 std::vector<DataProcessorSpec> importedWorkflow;
3022 bool previousWorked = WorkflowSerializationHelpers::import(std::cin, importedWorkflow, dataProcessorInfos, commandInfo);
3023 if (previousWorked == false) {
3024 exit(1);
3025 }
3026
3027 size_t workflowHashB = 0;
3028 for (auto& dp : importedWorkflow) {
3029 workflowHashB += hash_fn(dp.name);
3030 }
3031
3032 // FIXME: Streamline...
3033 // We remove the duplicates because for the moment child get themself twice:
3034 // once from the actual definition in the child, a second time from the
3035 // configuration they get passed by their parents.
3036 for (auto& dp : importedWorkflow) {
3037 auto found = std::find_if(physicalWorkflow.begin(), physicalWorkflow.end(),
3038 [&name = dp.name](DataProcessorSpec const& spec) { return spec.name == name; });
3039 if (found == physicalWorkflow.end()) {
3040 physicalWorkflow.push_back(dp);
3041 rankIndex.insert(std::make_pair(dp.name, workflowHashB));
3042 } else if (DataProcessorSpecHelpers::isPlaceholder(*found)) {
3043 auto algorithm = std::move(found->algorithm);
3044 *found = dp;
3045 found->algorithm = std::move(algorithm);
3046 }
3047 }
3048 }
3049 for (auto& dp : physicalWorkflow) {
3051 LOGP(fatal, "{} was not built, but the driver did not pass its spec.", dp.name);
3052 }
3053 }
3054
3059 for (auto& dp : physicalWorkflow) {
3060 auto isExpendable = [](DataProcessorLabel const& label) { return label.value == "expendable" || label.value == "non-critical"; };
3061 if (std::find_if(dp.labels.begin(), dp.labels.end(), isExpendable) != dp.labels.end()) {
3062 for (auto& output : dp.outputs) {
3063 if (output.lifetime == Lifetime::Timeframe) {
3064 output.lifetime = Lifetime::Sporadic;
3065 }
3066 }
3067 }
3068 }
3069
3071 OverrideServiceSpecs driverServicesOverride = ServiceSpecHelpers::parseOverrides(getenv("DPL_DRIVER_OVERRIDE_SERVICES"));
3073 // We insert the hash for the internal devices.
3074 WorkflowHelpers::injectServiceDevices(physicalWorkflow, configContext);
3075 auto& dec = configContext.services().get<DanglingEdgesContext>();
3076 if (!(dec.requestedAODs.empty() && dec.requestedDYNs.empty() && dec.requestedIDXs.empty() && dec.requestedTIMs.empty())) {
3077 driverServices.push_back(ArrowSupport::arrowBackendSpec());
3078 }
3079 for (auto& service : driverServices) {
3080 if (service.injectTopology == nullptr) {
3081 continue;
3082 }
3083 WorkflowSpecNode node{physicalWorkflow};
3084 service.injectTopology(node, configContext);
3085 }
3086 for (auto& dp : physicalWorkflow) {
3087 if (dp.name.rfind("internal-", 0) == 0) {
3088 rankIndex.insert(std::make_pair(dp.name, hash_fn("internal")));
3089 }
3090 }
3091
3092 // We sort dataprocessors and Inputs / outputs by name, so that the edges are
3093 // always in the same order.
3094 std::stable_sort(physicalWorkflow.begin(), physicalWorkflow.end(), [](DataProcessorSpec const& a, DataProcessorSpec const& b) {
3095 return a.name < b.name;
3096 });
3097
3098 for (auto& dp : physicalWorkflow) {
3099 std::stable_sort(dp.inputs.begin(), dp.inputs.end(),
3100 [](InputSpec const& a, InputSpec const& b) { return DataSpecUtils::describe(a) < DataSpecUtils::describe(b); });
3101 std::stable_sort(dp.outputs.begin(), dp.outputs.end(),
3102 [](OutputSpec const& a, OutputSpec const& b) { return DataSpecUtils::describe(a) < DataSpecUtils::describe(b); });
3103 }
3104
3105 // Create a list of all the edges, so that we can do a topological sort
3106 // before we create the graph.
3107 std::vector<std::pair<int, int>> edges;
3108
3109 if (physicalWorkflow.size() > 1) {
3110 edges = TopologyPolicyHelpers::buildEdges(physicalWorkflow);
3111
3112 auto topoInfos = WorkflowHelpers::topologicalSort(physicalWorkflow.size(), &edges[0].first, &edges[0].second, sizeof(std::pair<int, int>), edges.size());
3113 if (topoInfos.size() != physicalWorkflow.size()) {
3114 // Check missing resilincy of one of the tasks
3115 checkNonResiliency(physicalWorkflow, edges);
3116 throw std::runtime_error("Unable to do topological sort of the resulting workflow. Do you have loops?\n" + debugTopoInfo(physicalWorkflow, topoInfos, edges));
3117 }
3118 // Sort by layer and then by name, to ensure stability.
3119 std::stable_sort(topoInfos.begin(), topoInfos.end(), [&workflow = physicalWorkflow](TopoIndexInfo const& a, TopoIndexInfo const& b) {
3120 auto aRank = std::make_tuple(a.layer, -workflow.at(a.index).outputs.size(), workflow.at(a.index).name);
3121 auto bRank = std::make_tuple(b.layer, -workflow.at(b.index).outputs.size(), workflow.at(b.index).name);
3122 return aRank < bRank;
3123 });
3124 // Reverse index and apply the result
3125 std::vector<int> dataProcessorOrder;
3126 dataProcessorOrder.resize(topoInfos.size());
3127 for (size_t i = 0; i < topoInfos.size(); ++i) {
3128 dataProcessorOrder[topoInfos[i].index] = i;
3129 }
3130 std::vector<int> newLocations;
3131 newLocations.resize(dataProcessorOrder.size());
3132 for (size_t i = 0; i < dataProcessorOrder.size(); ++i) {
3133 newLocations[dataProcessorOrder[i]] = i;
3134 }
3135 apply_permutation(physicalWorkflow, newLocations);
3136 }
3137
3138 // Use the hidden options as veto, all config specs matching a definition
3139 // in the hidden options are skipped in order to avoid duplicate definitions
3140 // in the main parser. Note: all config specs are forwarded to devices
3141 visibleOptions.add(ConfigParamsHelper::prepareOptionDescriptions(physicalWorkflow, currentWorkflowOptions, gHiddenDeviceOptions));
3142
3143 bpo::options_description od;
3144 od.add(visibleOptions);
3145 od.add(gHiddenDeviceOptions);
3146
3147 // FIXME: decide about the policy for handling unrecognized arguments
3148 // command_line_parser with option allow_unregistered() can be used
3149 using namespace bpo::command_line_style;
3150 auto style = (allow_short | short_allow_adjacent | short_allow_next | allow_long | long_allow_adjacent | long_allow_next | allow_sticky | allow_dash_for_short);
3151 bpo::variables_map varmap;
3152 try {
3153 bpo::store(
3154 bpo::command_line_parser(argc, argv)
3155 .options(od)
3156 .style(style)
3157 .run(),
3158 varmap);
3159 } catch (std::exception const& e) {
3160 LOGP(error, "error parsing options of {}: {}", argv[0], e.what());
3161 exit(1);
3162 }
3163 conflicting_options(varmap, "dds", "o2-control");
3164 conflicting_options(varmap, "dds", "dump-workflow");
3165 conflicting_options(varmap, "dds", "run");
3166 conflicting_options(varmap, "dds", "graphviz");
3167 conflicting_options(varmap, "o2-control", "dump-workflow");
3168 conflicting_options(varmap, "o2-control", "run");
3169 conflicting_options(varmap, "o2-control", "graphviz");
3170 conflicting_options(varmap, "run", "dump-workflow");
3171 conflicting_options(varmap, "run", "graphviz");
3172 conflicting_options(varmap, "run", "mermaid");
3173 conflicting_options(varmap, "dump-workflow", "graphviz");
3174 conflicting_options(varmap, "no-batch", "batch");
3175
3176 if (varmap.count("help")) {
3177 printHelp(varmap, executorOptions, physicalWorkflow, currentWorkflowOptions);
3178 exit(0);
3179 }
3183 if (varmap.count("severity")) {
3184 auto logLevel = varmap["severity"].as<std::string>();
3185 if (logLevel == "debug") {
3186 fair::Logger::SetConsoleSeverity(fair::Severity::debug);
3187 } else if (logLevel == "detail") {
3188 fair::Logger::SetConsoleSeverity(fair::Severity::detail);
3189 } else if (logLevel == "info") {
3190 fair::Logger::SetConsoleSeverity(fair::Severity::info);
3191 } else if (logLevel == "warning") {
3192 fair::Logger::SetConsoleSeverity(fair::Severity::warning);
3193 } else if (logLevel == "error") {
3194 fair::Logger::SetConsoleSeverity(fair::Severity::error);
3195 } else if (logLevel == "important") {
3196 fair::Logger::SetConsoleSeverity(fair::Severity::important);
3197 } else if (logLevel == "alarm") {
3198 fair::Logger::SetConsoleSeverity(fair::Severity::alarm);
3199 } else if (logLevel == "critical") {
3200 fair::Logger::SetConsoleSeverity(fair::Severity::critical);
3201 } else if (logLevel == "fatal") {
3202 fair::Logger::SetConsoleSeverity(fair::Severity::fatal);
3203 } else {
3204 LOGP(error, "Invalid log level '{}'", logLevel);
3205 exit(1);
3206 }
3207 }
3208
3209 if (varmap["log-timestamp-us"].as<bool>()) {
3210 fair::Logger::DefineVerbosity(fair::Verbosity::user1,
3211 fair::VerbositySpec::Make(fair::VerbositySpec::Info::timestamp_us,
3212 fair::VerbositySpec::Info::severity));
3213 fair::Logger::SetVerbosity(fair::Verbosity::user1);
3214 }
3215
3216 enableSignposts(varmap["signposts"].as<std::string>());
3217
3218 auto evaluateBatchOption = [&varmap]() -> bool {
3219 if (varmap.count("no-batch") > 0) {
3220 return false;
3221 }
3222 if (varmap.count("batch") == 0) {
3223 // default value
3224 return isatty(fileno(stdout)) == 0;
3225 }
3226 // FIXME: should actually use the last value, but for some reason the
3227 // values are not filled into the vector, even if specifying `-b true`
3228 // need to find out why the boost program options example is not working
3229 // in our case. Might depend on the parser options
3230 // auto value = varmap["batch"].as<std::vector<std::string>>();
3231 return true;
3232 };
3233 DriverInfo driverInfo{
3234 .sendingPolicies = sendingPolicies,
3235 .forwardingPolicies = forwardingPolicies,
3236 .callbacksPolicies = callbacksPolicies};
3237 driverInfo.states.reserve(10);
3238 driverInfo.sigintRequested = false;
3239 driverInfo.sigchldRequested = false;
3240 driverInfo.channelPolicies = channelPolicies;
3241 driverInfo.completionPolicies = completionPolicies;
3242 driverInfo.dispatchPolicies = dispatchPolicies;
3243 driverInfo.resourcePolicies = resourcePolicies;
3244 driverInfo.argc = argc;
3245 driverInfo.argv = argv;
3246 driverInfo.noSHMCleanup = varmap["no-cleanup"].as<bool>();
3247 driverInfo.processingPolicies.termination = varmap["completion-policy"].as<TerminationPolicy>();
3248 driverInfo.processingPolicies.earlyForward = varmap["early-forward-policy"].as<EarlyForwardPolicy>();
3249 driverInfo.mode = varmap["driver-mode"].as<DriverMode>();
3250
3251 auto batch = evaluateBatchOption();
3252 DriverConfig driverConfig{
3253 .batch = batch,
3254 .driverHasGUI = (batch == false) || getenv("DPL_DRIVER_REMOTE_GUI") != nullptr,
3255 };
3256
3257 if (varmap["error-policy"].defaulted() && driverConfig.batch == false) {
3258 driverInfo.processingPolicies.error = TerminationPolicy::WAIT;
3259 } else {
3260 driverInfo.processingPolicies.error = varmap["error-policy"].as<TerminationPolicy>();
3261 }
3262 driverInfo.minFailureLevel = varmap["min-failure-level"].as<LogParsingHelpers::LogLevel>();
3263 driverInfo.startTime = uv_hrtime();
3264 driverInfo.startTimeMsFromEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(
3265 std::chrono::system_clock::now().time_since_epoch())
3266 .count();
3267 driverInfo.timeout = varmap["timeout"].as<uint64_t>();
3268 driverInfo.deployHostname = varmap["hostname"].as<std::string>();
3269 driverInfo.resources = varmap["resources"].as<std::string>();
3270 driverInfo.resourcesMonitoringInterval = varmap["resources-monitoring"].as<unsigned short>();
3271 driverInfo.resourcesMonitoringFilename = varmap["resources-monitoring-file"].as<std::string>();
3272 driverInfo.resourcesMonitoringDumpInterval = varmap["resources-monitoring-dump-interval"].as<unsigned short>();
3273
3274 // FIXME: should use the whole dataProcessorInfos, actually...
3275 driverInfo.processorInfo = dataProcessorInfos;
3276 driverInfo.configContext = &configContext;
3277
3278 DriverControl driverControl;
3279 initialiseDriverControl(varmap, driverInfo, driverControl);
3280
3281 commandInfo.merge(CommandInfo(argc, argv));
3282
3283 std::string frameworkId;
3284 // If the id is set, this means this is a device,
3285 // otherwise this is the driver.
3286 if (varmap.count("id")) {
3287 // The framework id does not want to know anything about DDS template expansion
3288 // so we simply drop it. Notice that the "id" Property is still the same as the
3289 // original --id option.
3290 frameworkId = std::regex_replace(varmap["id"].as<std::string>(), std::regex{"_dds.*"}, "");
3291 driverInfo.uniqueWorkflowId = fmt::format("{}", getppid());
3292 driverInfo.defaultDriverClient = "stdout://";
3293 } else {
3294 driverInfo.uniqueWorkflowId = fmt::format("{}", getpid());
3295 driverInfo.defaultDriverClient = "ws://";
3296 }
3297 return runStateMachine(physicalWorkflow,
3298 currentWorkflow,
3299 dataProcessorInfos,
3300 commandInfo,
3301 driverControl,
3302 driverInfo,
3303 driverConfig,
3305 detectedParams,
3306 varmap,
3307 driverServices,
3308 frameworkId);
3309}
3310
3311void doBoostException(boost::exception&, char const* processName)
3312{
3313 LOGP(error, "error while setting up workflow in {}: {}",
3314 processName, boost::current_exception_diagnostic_information(true));
3315}
3316#pragma GCC diagnostic push
struct uv_timer_s uv_timer_t
struct uv_async_s uv_async_t
struct uv_handle_s uv_handle_t
struct uv_poll_s uv_poll_t
struct uv_loop_s uv_loop_t
std::vector< OutputRoute > routes
std::ostringstream debug
std::unique_ptr< expressions::Node > node
unsigned short int uint16_t
int32_t i
int32_t retVal
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has processed
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has before it waits for the display Opacity of the console background Number of console lines the history is shortened to this when it reaches twice the size Time in seconds an info message fades out after setInfoTime h OpenGL display display Font size Do not use Freetype for font Renderer for event Frontend to use for event ROOT macros used as track filter Console script file
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
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 d
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 int32_t
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
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 option
char error[CURL_ERROR_SIZE]
CURL * handle
std::vector< o2::MCCompLabel > labels
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
double maxDepth
std::vector< SidecarEdge > edges
o2::phos::PHOSEnergySlot es
uint16_t pos
Definition RawData.h:3
uint16_t pid
Definition RawData.h:2
#define O2_SIGNPOST_EVENT_EMIT_ERROR(log, id, name, format,...)
Definition Signpost.h:554
o2_log_handle_t * o2_walk_logs(bool(*callback)(char const *name, void *log, void *context), void *context=nullptr)
#define O2_DECLARE_DYNAMIC_LOG(name)
Definition Signpost.h:490
#define O2_SIGNPOST_ID_FROM_POINTER(name, log, pointer)
Definition Signpost.h:506
#define O2_SIGNPOST_EVENT_EMIT_INFO(log, id, name, format,...)
Definition Signpost.h:532
#define O2_SIGNPOST_END(log, id, name, format,...)
Definition Signpost.h:609
void _o2_log_set_stacktrace(_o2_log_t *log, int stacktrace)
#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
o2::monitoring::Monitoring Monitoring
StringRef key
ServiceRegistryRef services() const
static ServiceRegistryRef * globalDeviceRef(ServiceRegistryRef *ref=nullptr)
void registerService(ServiceTypeHash typeHash, void *service, ServiceKind kind, char const *name=nullptr) const
bool match(const std::vector< std::string > &queries, const char *pattern)
Definition dcs-ccdb.cxx:229
GLenum mode
Definition glcorearb.h:266
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum GLenum dst
Definition glcorearb.h:1767
GLboolean * data
Definition glcorearb.h:298
GLintptr offset
Definition glcorearb.h:660
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLsizei GLenum const void * indices
Definition glcorearb.h:400
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLboolean r
Definition glcorearb.h:1233
GLenum GLenum GLsizei len
Definition glcorearb.h:4232
GLenum GLfloat param
Definition glcorearb.h:271
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLuint GLuint stream
Definition glcorearb.h:1806
GLint ref
Definition glcorearb.h:291
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition glcorearb.h:2514
GLuint id
Definition glcorearb.h:650
GLuint counter
Definition glcorearb.h:3987
uint8_t int statusCode int
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
std::vector< ServiceSpec > ServiceSpecs
RuntimeErrorRef runtime_error(const char *)
EarlyForwardPolicy
When to enable the early forwarding optimization:
std::vector< OverrideServiceSpec > OverrideServiceSpecs
void parse_http_request(char *start, size_t size, HTTPParser *parser)
RuntimeError & error_from_ref(RuntimeErrorRef)
std::vector< DataProcessorSpec > WorkflowSpec
AlgorithmSpec::ProcessCallback adaptStateless(LAMBDA l)
RuntimeErrorRef runtime_error_f(const char *,...)
void dumpDeviceSpec2O2Control(std::string workflowName, std::vector< DeviceSpec > const &specs, std::vector< DeviceExecution > const &executions, CommandInfo const &commandInfo)
Dumps the AliECS compatible workflow and task templates for a DPL workflow.
if(!okForPhiMin(phi0, phi1))
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::string filename()
void empty(int)
std::vector< std::string > split(const std::string &str, char delimiter=',')
int runStateMachine(DataProcessorSpecs const &workflow, WorkflowInfo const &workflowInfo, DataProcessorInfos const &previousDataProcessorInfos, CommandInfo const &commandInfo, DriverControl &driverControl, DriverInfo &driverInfo, DriverConfig &driverConfig, std::vector< DeviceMetricsInfo > &metricsInfos, std::vector< ConfigParamSpec > const &detectedParams, boost::program_options::variables_map &varmap, std::vector< ServiceSpec > &driverServices, std::string frameworkId)
AlgorithmSpec dryRun(DeviceSpec const &spec)
auto bindGUIPort
void getChildData(int infd, DeviceInfo &outinfo)
void overrideLabels(ConfigContext &ctx, WorkflowSpec &workflow)
void apply_permutation(std::vector< T > &v, std::vector< int > &indices)
int doMain(int argc, char **argv, o2::framework::WorkflowSpec const &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 &sendingPolicies, std::vector< ConfigParamSpec > const &currentWorkflowOptions, std::vector< ConfigParamSpec > const &detectedParams, o2::framework::ConfigContext &configContext)
void overridePipeline(ConfigContext &ctx, WorkflowSpec &workflow)
void enableSignposts(std::string const &signpostsToEnable)
void spawnDevice(uv_loop_t *loop, DeviceRef ref, std::vector< DeviceSpec > const &specs, DriverInfo &driverInfo, std::vector< DeviceControl > &, std::vector< DeviceExecution > &executions, std::vector< DeviceInfo > &deviceInfos, std::vector< DataProcessingStates > &allStates, ServiceRegistryRef serviceRegistry, boost::program_options::variables_map &varmap, std::vector< DeviceStdioContext > &childFds, unsigned parentCPU, unsigned parentNode)
void killChildren(std::vector< DeviceInfo > &infos, int sig)
void ws_connect_callback(uv_stream_t *server, int status)
A callback for the rest engine.
std::vector< DataProcessingStates > DataProcessingStatesInfos
void createPipes(int *pipes)
void doDPLException(o2::framework::RuntimeErrorRef &ref, char const *)
std::vector< DeviceExecution > DeviceExecutions
void overrideAll(o2::framework::ConfigContext &ctx, std::vector< o2::framework::DataProcessorSpec > &workflow)
std::vector< DeviceMetricsInfo > gDeviceMetricsInfos
void force_exit_callback(uv_timer_s *ctx)
std::string debugTopoInfo(std::vector< DataProcessorSpec > const &specs, std::vector< TopoIndexInfo > const &infos, std::vector< std::pair< int, int > > const &edges)
void overrideCloning(ConfigContext &ctx, WorkflowSpec &workflow)
void doBoostException(boost::exception &e, const char *)
bool processSigChild(DeviceInfos &infos, DeviceSpecs &specs)
void checkNonResiliency(std::vector< DataProcessorSpec > const &specs, std::vector< std::pair< int, int > > const &edges)
std::vector< std::regex > getDumpableMetrics()
void stream_config(uv_work_t *req)
std::vector< DataProcessorSpec > DataProcessorSpecs
void dumpRunSummary(DriverServerContext &context, DriverInfo const &driverInfo, DeviceInfos const &infos, DeviceSpecs const &specs)
void conflicting_options(const boost::program_options::variables_map &vm, const std::string &opt1, const std::string &opt2)
Helper to to detect conflicting options.
void doDefaultWorkflowTerminationHook()
bool checkIfCanExit(std::vector< DeviceInfo > const &infos)
volatile sig_atomic_t sigchld_requested
bool isOutputToPipe()
void handleSignals()
std::vector< DeviceSpec > DeviceSpecs
std::vector< DataProcessorInfo > DataProcessorInfos
volatile sig_atomic_t forceful_exit
bool areAllChildrenGone(std::vector< DeviceInfo > &infos)
Check the state of the children.
std::vector< DeviceControl > DeviceControls
volatile sig_atomic_t double_sigint
void close_websocket(uv_handle_t *handle)
void handleChildrenStdio(DriverServerContext *serverContext, std::string const &forwardedStdin, std::vector< DeviceStdioContext > &childFds, std::vector< uv_poll_t * > &handles)
char * getIdString(int argc, char **argv)
bool isInputConfig()
std::unique_ptr< o2::framework::ServiceRegistry > createRegistry()
void log_callback(uv_poll_t *handle, int status, int events)
void processChildrenOutput(uv_loop_t *loop, DriverInfo &driverInfo, DeviceInfos &infos, DeviceSpecs const &specs, DeviceControls &controls)
volatile sig_atomic_t graceful_exit
void single_step_callback(uv_timer_s *ctx)
Force single stepping of the children.
bpo::options_description gHiddenDeviceOptions("Hidden child options")
void doUnknownException(std::string const &s, char const *)
int doChild(int argc, char **argv, ServiceRegistry &serviceRegistry, DanglingEdgesContext &danglingEdgesContext, RunningWorkflowInfo const &runningWorkflow, RunningDeviceRef ref, DriverConfig const &driverConfig, ProcessingPolicies processingPolicies, std::string const &defaultDriverClient, uv_loop_t *loop)
o2::framework::ConfigContext createConfigContext(std::unique_ptr< ConfigParamRegistry > &workflowOptionsRegistry, o2::framework::ServiceRegistry &configRegistry, std::vector< o2::framework::ConfigParamSpec > &workflowOptions, std::vector< o2::framework::ConfigParamSpec > &extraOptions, int argc, char **argv)
int callMain(int argc, char **argv, int(*mainNoCatch)(int, char **))
void handle_crash(int sig)
void dumpMetricsCallback(uv_timer_t *handle)
void cleanupSHM(std::string const &uniqueWorkflowId)
Helper to invoke shared memory cleanup.
void initialiseDriverControl(bpo::variables_map const &varmap, DriverInfo &driverInfo, DriverControl &control)
Helper function to initialise the controller from the command line options.
void gui_callback(uv_timer_s *ctx)
std::vector< DeviceInfo > DeviceInfos
void printHelp(bpo::variables_map const &varmap, bpo::options_description const &executorOptions, std::vector< DataProcessorSpec > const &physicalWorkflow, std::vector< ConfigParamSpec > const &currentWorkflowOptions)
void spawnRemoteDevice(uv_loop_t *loop, std::string const &, DeviceSpec const &spec, DeviceControl &, DeviceExecution &, DeviceInfos &deviceInfos, DataProcessingStatesInfos &allStates)
void websocket_callback(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
int mainNoCatch(int argc, char **argv)
DriverServerContext * serverContext
std::vector< ConfigParamSpec > options
std::vector< std::string > args
static ServiceSpec arrowBackendSpec()
static void demangled_backtrace_symbols(void **backtrace, unsigned int total, int fd)
static std::vector< ServiceSpec > defaultServices()
static std::vector< ComputingResource > parseResources(std::string const &resourceString)
static std::vector< ConfigParamSpec > discover(ConfigParamRegistry &, int, char **)
static boost::program_options::options_description prepareOptionDescriptions(ContainerType const &workflow, std::vector< ConfigParamSpec > const &currentWorkflowOptions, options_description vetos=options_description(), std::string mode="full")
populate boost program options for a complete workflow
static void populateBoostProgramOptions(options_description &options, const std::vector< ConfigParamSpec > &specs, options_description vetos=options_description())
static void dumpDeviceSpec2DDS(std::ostream &out, DriverMode mode, std::string const &workflowSuffix, std::vector< DataProcessorSpec > const &workflow, std::vector< DataProcessorInfo > const &metadata, std::vector< DeviceSpec > const &specs, std::vector< DeviceExecution > const &executions, CommandInfo const &commandInfo)
static void preExitCallbacks(std::vector< ServiceExitHandle >, ServiceRegistryRef)
Invoke callback to be executed on exit, in reverse order.
A label that can be associated to a DataProcessorSpec.
static bool isPlaceholder(DataProcessorSpec const &spec)
static std::string describe(InputSpec const &spec)
Plugin interface for DPL GUIs.
Definition DebugGUI.h:30
virtual void * initGUI(char const *windowTitle, ServiceRegistry &registry)=0
virtual std::function< void(void)> getGUIDebugger(std::vector< o2::framework::DeviceInfo > const &infos, std::vector< o2::framework::DeviceSpec > const &devices, std::vector< o2::framework::DataProcessingStates > const &allStates, std::vector< o2::framework::DataProcessorInfo > const &metadata, std::vector< o2::framework::DeviceMetricsInfo > const &metricsInfos, o2::framework::DriverInfo const &driverInfo, std::vector< o2::framework::DeviceControl > &controls, o2::framework::DriverControl &driverControl)=0
static DeploymentMode deploymentMode()
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)
ProcessingPolicies & processingPolicies
char logFilter[MAX_USER_FILTER_SIZE]
Lines in the log should match this to be displayed.
bool quiet
wether we should be capturing device output.
std::string unprinted
An unterminated string which is not ready to be printed yet.
Definition DeviceInfo.h:63
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 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 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< OutputRoute > outputs
Definition DeviceSpec.h:63
std::string id
The id of the device, including time-pipelining and suffix.
Definition DeviceSpec.h:52
static int parseTracingFlags(std::string const &events)
std::vector< DeviceControl > & controls
bool batch
Whether the driver was started in batch mode or not.
std::vector< DriverState > forcedTransitions
std::vector< Callback > callbacks
DriverControlState state
Current state of the state machine player.
std::vector< DeviceSpec > * specs
std::vector< ServiceSummaryHandling > * summaryCallbacks
std::vector< DeviceMetricsInfo > * metrics
std::vector< DeviceInfo > * infos
static std::vector< ForwardingPolicy > createDefaultPolicies()
static void dumpDeviceSpec2Graphviz(std::ostream &, const Devices &specs)
Helper to dump a set of devices as a graphviz file.
static void dumpDataProcessorSpec2Graphviz(std::ostream &, const WorkflowSpec &specs, std::vector< std::pair< int, int > > const &edges={})
Helper to dump a workflow as a graphviz file.
std::string binding
A mnemonic name for the input spec.
Definition InputSpec.h:66
LogLevel
Possible log levels for device log entries.
static LogLevel parseTokenLevel(std::string_view const s)
static void dumpDeviceSpec2Mermaid(std::ostream &, const Devices &specs)
Helper to dump a set of devices as a mermaid file.
Temporary struct to hold a metric after it has been parsed.
static bool dumpMetricsToJSON(std::vector< DeviceMetricsInfo > const &metrics, DeviceMetricsInfo const &driverMetrics, std::vector< DeviceSpec > const &specs, std::vector< std::regex > const &metricsToDump, std::ostream &out) noexcept
static bool isResourcesMonitoringEnabled(unsigned short interval) noexcept
Information about the running workflow.
void declareService(ServiceSpec const &spec, DeviceState &state, fair::mq::ProgOptions &options, ServiceRegistry::Salt salt=ServiceRegistry::globalDeviceSalt())
void registerService(ServiceTypeHash typeHash, void *service, ServiceKind kind, Salt salt, char const *name=nullptr, ServiceRegistry::SpecIndex specIndex=SpecIndex{-1}) const
static OverrideServiceSpecs parseOverrides(char const *overrideString)
static ServiceSpecs filterDisabled(ServiceSpecs originals, OverrideServiceSpecs const &overrides)
static std::function< int64_t(int64_t base, int64_t offset)> defaultCPUTimeConfigurator(uv_loop_t *loop)
static std::function< void(int64_t &base, int64_t &offset)> defaultRealtimeBaseConfigurator(uint64_t offset, uv_loop_t *loop)
Helper struct to keep track of the results of the topological sort.
static auto buildEdges(WorkflowSpec &physicalWorkflow) -> std::vector< std::pair< int, int > >
static void adjustTopology(WorkflowSpec &workflow, ConfigContext const &ctx)
static void injectServiceDevices(WorkflowSpec &workflow, ConfigContext &ctx)
static WorkflowParsingState verifyWorkflow(const WorkflowSpec &workflow)
static std::vector< TopoIndexInfo > topologicalSort(size_t nodeCount, int const *edgeIn, int const *edgeOut, size_t byteStride, size_t edgesCount)
static void dump(std::ostream &o, std::vector< DataProcessorSpec > const &workflow, std::vector< DataProcessorInfo > const &metadata, CommandInfo const &commandInfo)
static bool import(std::istream &s, std::vector< DataProcessorSpec > &workflow, std::vector< DataProcessorInfo > &metadata, CommandInfo &command)
uint16_t de
void * malloc(size_t n)
void free(void *p)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< ChannelData > channels
uint64_t const void const *restrict const msg
Definition x9.h:153