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