3 #define TRACEMF_USE_VERBATIM 1
4 #include "otsdaq/ARTDAQSupervisor/ARTDAQSupervisor.hh"
6 #include "artdaq-core/Utilities/configureMessageFacility.hh"
7 #include "artdaq/BuildInfo/GetPackageBuildInfo.hh"
8 #include "artdaq/DAQdata/Globals.hh"
9 #include "artdaq/ExternalComms/MakeCommanderPlugin.hh"
10 #include "cetlib_except/exception.h"
11 #include "fhiclcpp/make_ParameterSet.h"
12 #include "otsdaq/ARTDAQSupervisor/ARTDAQSupervisorTRACEController.h"
14 #include "artdaq-core/Utilities/ExceptionHandler.hh"
16 #include <boost/exception/all.hpp>
17 #include <boost/filesystem.hpp>
25 #include "otsdaq/ConfigurationInterface/ConfigurationInterface.h"
26 #include "otsdaq/Macros/StringMacros.h"
27 #include "otsdaq/TableCore/TableBase.h"
29 #define OUT_ON_ERR_SIZE 2000
31 #define HALT_RUNNER_STOP_TIMEOUT_SECONDS 5
33 #define HALT_PYTHON_MUTEX_TIMEOUT_SECONDS 5
35 #define DESTROY_RUNNER_STOP_TIMEOUT_SECONDS 5
37 #define RUNNER_MAX_CONSECUTIVE_STATUS_FAILURES 5
43 const std::string ARTDAQSupervisor::ARTDAQ_SYSVAR_NAMESPACE =
"artdaq";
45 #define FAKE_CONFIG_NAME "ots_config"
46 #define DAQINTERFACE_PORT \
47 std::atoi(__ENV__("ARTDAQ_BASE_PORT")) + \
48 (partition_ * std::atoi(__ENV__("ARTDAQ_PORTS_PER_PARTITION")))
51 static std::unordered_map<int, struct sigaction> old_actions =
52 std::unordered_map<int, struct sigaction>();
53 static bool sighandler_init =
false;
54 static void signal_handler(
int signum)
57 #if TRACE_REVNUM < 1459
58 TRACE_STREAMER(TLVL_ERROR, &(
"ARTDAQsupervisor")[0], 0, 0, 0)
60 TRACE_STREAMER(TLVL_ERROR, TLOG2(
"ARTDAQsupervisor", 0), 0)
62 <<
"A signal of type " << signum
63 <<
" was caught by ARTDAQSupervisor. Shutting down DAQInterface, "
64 "then proceeding with default handlers!";
70 pthread_sigmask(SIG_UNBLOCK, NULL, &set);
71 pthread_sigmask(SIG_UNBLOCK, &set, NULL);
73 #if TRACE_REVNUM < 1459
74 TRACE_STREAMER(TLVL_ERROR, &(
"ARTDAQsupervisor")[0], 0, 0, 0)
76 TRACE_STREAMER(TLVL_ERROR, TLOG2(
"ARTDAQsupervisor", 0), 0)
78 <<
"Calling default signal handler";
81 sigaction(signum, &old_actions[signum], NULL);
82 kill(getpid(), signum);
88 sigaction(SIGINT, &old_actions[SIGINT], NULL);
89 kill(getpid(), SIGINT);
95 static std::mutex sighandler_mutex;
96 std::unique_lock<std::mutex> lk(sighandler_mutex);
100 sighandler_init =
true;
102 std::vector<int> signals = {
113 for(
auto signal : signals)
115 struct sigaction old_action;
116 sigaction(signal, NULL, &old_action);
120 if(old_action.sa_handler != SIG_IGN)
122 struct sigaction action;
123 action.sa_handler = signal_handler;
124 sigemptyset(&action.sa_mask);
125 for(
auto sigblk : signals)
127 sigaddset(&action.sa_mask, sigblk);
133 sigaction(signal, &action, NULL);
134 old_actions[signal] = old_action;
141 ARTDAQSupervisor::ARTDAQSupervisor(xdaq::ApplicationStub* stub)
143 , daqinterface_ptr_(NULL)
144 , partition_(getSupervisorProperty(
"partition", 0))
145 , daqinterface_state_(
"notrunning")
146 , runner_thread_(nullptr)
148 __SUP_COUT__ <<
"Constructor." << __E__;
151 init_sighandler(
this);
158 auto settings_file = __ENV__(
"DAQINTERFACE_SETTINGS");
159 std::ofstream of(settings_file, std::ios::trunc);
160 const int openErrno = errno;
161 if(!of.is_open() || of.fail())
163 __SS__ <<
"Failed to open DAQINTERFACE_SETTINGS file '" << settings_file
164 <<
"' for writing: " << strerror(openErrno) << __E__;
169 setenv(
"DAQINTERFACE_PARTITION_NUMBER", std::to_string(partition_).c_str(), 1);
170 auto logfileName = std::string(__ENV__(
"OTSDAQ_LOG_DIR")) +
171 "/DAQInteface/DAQInterface_partition" +
172 std::to_string(partition_) +
".log";
173 setenv(
"DAQINTERFACE_LOGFILE", logfileName.c_str(), 1);
175 o <<
"log_directory: "
176 << getSupervisorProperty(
"log_directory", std::string(__ENV__(
"OTSDAQ_LOG_DIR")))
180 const std::string record_directory = getSupervisorProperty(
181 "record_directory", ARTDAQTableBase::ARTDAQ_FCL_PATH +
"/run_records/");
182 mkdir(record_directory.c_str(), 0755);
183 o <<
"record_directory: " << record_directory << std::endl;
186 o <<
"package_hashes_to_save: "
187 << getSupervisorProperty(
"package_hashes_to_save",
"[artdaq]") << std::endl;
189 o <<
"spack_root_for_bash_scripts: "
190 << getSupervisorProperty(
"spack_root_for_bash_scripts",
191 std::string(__ENV__(
"SPACK_ROOT")))
193 o <<
"boardreader timeout: " << getSupervisorProperty(
"boardreader_timeout", 30)
195 o <<
"eventbuilder timeout: " << getSupervisorProperty(
"eventbuilder_timeout", 30)
197 o <<
"datalogger timeout: " << getSupervisorProperty(
"datalogger_timeout", 30)
199 o <<
"dispatcher timeout: " << getSupervisorProperty(
"dispatcher_timeout", 30)
202 if(!getSupervisorProperty(
"advanced_memory_usage",
false))
204 o <<
"max_fragment_size_bytes: "
205 << getSupervisorProperty(
"max_fragment_size_bytes", 1048576) << std::endl;
207 o <<
"transfer_plugin_to_use: "
208 << getSupervisorProperty(
"transfer_plugin_to_use",
"TCPSocket") << std::endl;
209 if(getSupervisorProperty(
"transfer_plugin_from_brs",
"") !=
"")
211 o <<
"transfer_plugin_from_brs: "
212 << getSupervisorProperty(
"transfer_plugin_from_brs",
"") << std::endl;
214 if(getSupervisorProperty(
"transfer_plugin_from_ebs",
"") !=
"")
216 o <<
"transfer_plugin_from_ebs: "
217 << getSupervisorProperty(
"transfer_plugin_from_ebs",
"") << std::endl;
219 if(getSupervisorProperty(
"transfer_plugin_from_dls",
"") !=
"")
221 o <<
"transfer_plugin_from_dls: "
222 << getSupervisorProperty(
"transfer_plugin_from_dls",
"") << std::endl;
224 o <<
"all_events_to_all_dispatchers: " << std::boolalpha
225 << getSupervisorProperty(
"all_events_to_all_dispatchers",
true) << std::endl;
226 if(getSupervisorProperty(
"data_directory_override",
"") !=
"")
228 o <<
"data_directory_override: "
229 << getSupervisorProperty(
"data_directory_override",
"") << std::endl;
231 o <<
"max_configurations_to_list: "
232 << getSupervisorProperty(
"max_configurations_to_list", 10) << std::endl;
233 o <<
"disable_unique_rootfile_labels: "
234 << getSupervisorProperty(
"disable_unique_rootfile_labels",
false) << std::endl;
235 o <<
"use_messageviewer: " << std::boolalpha
236 << getSupervisorProperty(
"use_messageviewer",
false) << std::endl;
237 o <<
"use_messagefacility: " << std::boolalpha
238 << getSupervisorProperty(
"use_messagefacility",
true) << std::endl;
239 o <<
"fake_messagefacility: " << std::boolalpha
240 << getSupervisorProperty(
"fake_messagefacility",
false) << std::endl;
241 o <<
"kill_existing_processes: " << std::boolalpha
242 << getSupervisorProperty(
"kill_existing_processes",
true) << std::endl;
243 o <<
"advanced_memory_usage: " << std::boolalpha
244 << getSupervisorProperty(
"advanced_memory_usage",
false) << std::endl;
245 o <<
"strict_fragment_id_mode: " << std::boolalpha
246 << getSupervisorProperty(
"strict_fragment_id_mode",
false) << std::endl;
247 o <<
"disable_private_network_bookkeeping: " << std::boolalpha
248 << getSupervisorProperty(
"disable_private_network_bookkeeping",
false) << std::endl;
249 o <<
"allowed_processors: "
250 << getSupervisorProperty(
251 "allowed_processors",
254 if(getSupervisorProperty(
"partition_label_format",
"") !=
256 o <<
"partition_label_format: "
257 << getSupervisorProperty(
"partition_label_format",
"") << std::endl;
259 __COUT_MULTI__(0, o.str());
265 if(CorePropertySupervisorBase::theTRACEController_)
267 __SUP_COUT__ <<
"Destroying TRACE Controller..." << __E__;
268 delete CorePropertySupervisorBase::
270 CorePropertySupervisorBase::theTRACEController_ =
nullptr;
272 CorePropertySupervisorBase::theTRACEController_ =
275 ->setSupervisorPtr(
this);
277 __SUP_COUT__ <<
"Constructed." << __E__;
281 ARTDAQSupervisor::~ARTDAQSupervisor(
void)
283 __SUP_COUT__ <<
"Destructor." << __E__;
286 if(runner_abandoned_)
287 __SUP_COUT_WARN__ <<
"Skipping Py_Finalize(): a DAQInterface runner thread may "
288 "still be inside a Python call. The interpreter is leaked "
289 "rather than finalized under a live C-API call."
293 __SUP_COUT__ <<
"Calling Py_Finalize()" << __E__;
300 __SUP_COUT__ <<
"Destroying TRACE Controller..." << __E__;
305 __SUP_COUT__ <<
"Destructed." << __E__;
309 void ARTDAQSupervisor::destroy(
void)
311 __SUP_COUT__ <<
"Destroying..." << __E__;
321 if(!stop_runner_(DESTROY_RUNNER_STOP_TIMEOUT_SECONDS) || runner_abandoned_)
323 __SUP_COUT_WARN__ <<
"A DAQInterface runner thread is (or was earlier) abandoned "
324 "while possibly inside a Python call; skipping DAQInterface "
325 "recover and Python cleanup. artdaq processes may need "
331 if(daqinterface_ptr_ != NULL)
333 __SUP_COUT__ <<
"Calling recover transition" << __E__;
334 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
336 PyObjectGuard pName(PyUnicode_FromString(
"do_recover"));
338 PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), NULL));
340 __SUP_COUT__ <<
"Making sure that correct state has been reached" << __E__;
342 while(daqinterface_state_ !=
"stopped")
345 __SUP_COUT__ <<
"State is " << daqinterface_state_
346 <<
", waiting 1s and retrying..." << __E__;
351 Py_XDECREF(daqinterface_ptr_);
352 daqinterface_ptr_ = NULL;
355 __SUP_COUT__ <<
"Flusing printouts" << __E__;
358 PyRun_SimpleString(R
"(
360 sys.stdout = sys.__stdout__
361 sys.stderr = sys.__stderr__
367 stringIO_out_ =
nullptr;
368 stringIO_err_ =
nullptr;
370 __SUP_COUT__ <<
"Thread and garbage cleanup" << __E__;
373 "import threading; [t.join() for t in threading.enumerate() if t is not "
374 "threading.main_thread() and not isinstance(t, threading._DummyThread)]");
375 PyRun_SimpleString(
"import gc; gc.collect()");
379 __SUP_COUT__ <<
"Destroyed." << __E__;
383 void ARTDAQSupervisor::init(
void)
387 __SUP_COUT__ <<
"Initializing..." << __E__;
389 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
392 artdaq::configureMessageFacility(
"ARTDAQSupervisor");
393 __SUP_COUT__ <<
"artdaq MF configured." << __E__;
396 char* daqinterface_dir = getenv(
"ARTDAQ_DAQINTERFACE_DIR");
397 if(daqinterface_dir == NULL)
399 __SS__ <<
"ARTDAQ_DAQINTERFACE_DIR environment variable not set! This "
400 "means that DAQInterface has not been setup!"
406 __SUP_COUT__ <<
"Initializing Python" << __E__;
412 "from io import StringIO\n"
415 " def __init__(self, real, buf):\n"
416 " self.real = real\n"
418 " def write(self, data):\n"
419 " self.real.write(data)\n"
420 " self.buf.write(data)\n"
421 " def flush(self):\n"
422 " self.real.flush()\n"
423 " self.buf.flush()\n"
425 "tee_buffer = StringIO()\n"
426 "sys.stdout = TeeOut(sys.stdout, tee_buffer)\n"
427 "sys.stderr = TeeOut(sys.stderr, tee_buffer)\n");
429 __SUP_COUT__ <<
"Adding DAQInterface directory to PYTHON_PATH" << __E__;
430 PyObject* sysPath = PySys_GetObject(
432 PyObjectGuard programName(PyUnicode_FromString(daqinterface_dir));
433 PyList_Append(sysPath, programName.get());
435 __SUP_COUT__ <<
"Creating Module name" << __E__;
436 PyObjectGuard pName(PyUnicode_FromString(
"rc.control.daqinterface"));
439 __SUP_COUT__ <<
"Importing module" << __E__;
440 PyObjectGuard pModule(PyImport_Import(pName.get()));
442 if(pModule.get() == NULL)
444 std::string err = capturePyErr(
"import rc.control.daqinterface");
445 __SS__ <<
"Failed to load rc.control.daqinterface. Python Exception: "
451 __SUP_COUT__ <<
"Loading python module dictionary" << __E__;
452 PyObject* pDict = PyModule_GetDict(
456 std::string err = capturePyErr(
"module dict");
457 __SS__ <<
"Unable to load module dictionary. Python Exception: "
463 __SUP_COUT__ <<
"Getting DAQInterface object pointer" << __E__;
464 PyObject* di_obj_raw = PyDict_GetItemString(
465 pDict,
"DAQInterface");
466 if(di_obj_raw == NULL)
468 std::string err = capturePyErr(
"DAQInterface lookup");
469 __SS__ <<
"Unable to find 'DAQInterface' in module dictionary. "
474 Py_INCREF(di_obj_raw);
475 PyObjectGuard di_obj_ptr(di_obj_raw);
477 __SUP_COUT__ <<
"Filling out DAQInterface args struct" << __E__;
478 PyObjectGuard pArgs(PyTuple_New(0));
480 PyObjectGuard kwargs(Py_BuildValue(
"{s:s, s:s, s:i, s:i, s:s, s:s}",
494 __SUP_COUT__ <<
"Calling DAQInterface Object Constructor" << __E__;
497 PyObjectGuard sys(PyImport_ImportModule(
"sys"));
498 PyObjectGuard io(PyImport_ImportModule(
"io"));
505 stringIO_out_ = PyObject_CallMethod(io.get(),
"StringIO", NULL);
506 stringIO_err_ = PyObject_CallMethod(io.get(),
"StringIO", NULL);
513 PyObject_SetAttrString(sys.get(),
"stdout", stringIO_out_);
514 PyObject_SetAttrString(sys.get(),
"stderr", stringIO_err_);
520 PyImport_AddModule(
"__main__");
521 PyObject* globals = PyModule_GetDict(mainmod);
524 PyDict_GetItemString(globals,
"tee_buffer");
530 PyObject_Call(di_obj_ptr.get(), pArgs.get(), kwargs.get());
531 if(checkPythonError(daqinterface_ptr_))
533 std::string err = capturePyErr(
"DAQInterface constructor");
534 __SS__ <<
"DAQInterface constructor failed. Python Exception: "
543 PyObject_CallMethod(sys.get(),
"does_not_exist", NULL));
548 PyObjectGuard err_text(
549 PyObject_CallMethod(stringIO_err_,
"getvalue", NULL));
551 __COUT__ <<
"Captured stderr:\n"
552 << PyUnicode_AsUTF8(err_text.get()) <<
"\n";
554 __COUT__ <<
"Capture of stderr failed.";
585 initArtdaqSystemVariables();
587 __SUP_COUT__ <<
"Initialized." << __E__;
591 void ARTDAQSupervisor::transitionConfiguring(toolbox::Event::Reference )
593 __SUP_COUTT__ <<
"transitionConfiguring" << __E__;
595 loadArtdaqSystemVariables();
599 thread_error_message_ =
"";
600 thread_progress_bar_.resetProgressBar(0);
601 last_thread_progress_update_ = time(0);
607 CoreSupervisorBase::configureInit(
608 getSupervisorProperty(
"SkipRedundantConfigureActivation", 1) ==
612 std::thread(&ARTDAQSupervisor::configuringThread,
this).detach();
614 __SUP_COUT__ <<
"Configuring thread started." << __E__;
616 RunControlStateMachine::indicateIterationWork();
620 std::string errorMessage;
622 std::lock_guard<std::mutex> lock(
624 errorMessage = thread_error_message_;
626 int progress = thread_progress_bar_.
read();
627 __SUP_COUTVS__(2, errorMessage);
628 __SUP_COUTVS__(2, progress);
629 __SUP_COUTVS__(2, thread_progress_bar_.
isComplete());
632 if(errorMessage ==
"" &&
633 time(0) - last_thread_progress_update_ > 600)
635 __SUP_SS__ <<
"There has been no update from the configuration thread for "
636 << (time(0) - last_thread_progress_update_)
637 <<
" seconds, assuming something is wrong and giving up! "
638 <<
"Last progress received was " << progress << __E__;
639 errorMessage = ss.str();
646 if(errorMessage ==
"")
648 auto subapps = getSubappInfo();
649 for(
auto& subapp : subapps)
651 if(subapp.status == RunControlStateMachine::FAILED_STATE_NAME)
653 __SUP_SS__ <<
"Component '" << subapp.name
654 <<
"' entered Failed state during configuration! "
655 <<
"(url: " << subapp.url <<
")" << __E__;
656 errorMessage = ss.str();
657 __SUP_COUT_ERR__ <<
"\n" << ss.str();
663 if(errorMessage !=
"")
665 __SUP_SS__ <<
"Error was caught in configuring thread: " << errorMessage
667 __SUP_COUT_ERR__ <<
"\n" << ss.str();
669 theStateMachine_.setErrorMessage(ss.str());
670 throw toolbox::fsm::exception::Exception(
673 "CoreSupervisorBase::transitionConfiguring" ,
681 __SUP_COUTT__ <<
"Not done yet..." << __E__;
685 RunControlStateMachine::
686 indicateIterationWork();
688 if(last_thread_progress_read_ != progress)
690 last_thread_progress_read_ = progress;
691 last_thread_progress_update_ = time(0);
698 __SUP_COUT_INFO__ <<
"Complete configuring transition!" << __E__;
699 __SUP_COUTV__(getProcessInfo_());
707 void ARTDAQSupervisor::configuringThread()
710 std::string uid = theConfigurationManager_
711 ->
getNode(ConfigurationManager::XDAQ_APPLICATION_TABLE_NAME +
712 "/" + CorePropertySupervisorBase::getSupervisorUID() +
713 "/" +
"LinkToSupervisorTable")
716 __COUT__ <<
"Supervisor uid is " << uid <<
", getting supervisor table node" << __E__;
718 const std::string mfSubject_ = supervisorClassNoNamespace_ +
"-" + uid;
722 thread_progress_bar_.
step();
724 set_thread_message_(
"ConfigGen");
726 auto info = ARTDAQTableBase::extractARTDAQInfo(
730 getSupervisorProperty(
"max_fragment_size_bytes", 8888),
731 getSupervisorProperty(
"routing_timeout_ms", 1999),
732 getSupervisorProperty(
"routing_retry_count", 12),
733 &thread_progress_bar_);
736 if(info.processes.count(ARTDAQTableBase::ARTDAQAppType::BoardReader) == 0)
738 __GEN_SS__ <<
"There must be at least one enabled BoardReader!" << __E__;
741 if(info.processes.count(ARTDAQTableBase::ARTDAQAppType::EventBuilder) == 0)
743 __GEN_SS__ <<
"There must be at least one enabled EventBuilder!" << __E__;
747 thread_progress_bar_.
step();
748 set_thread_message_(
"Writing boot.txt");
750 __GEN_COUT__ <<
"Writing boot.txt" << __E__;
752 int debugLevel = theSupervisorNode.
getNode(
"DAQInterfaceDebugLevel").
getValue<
int>();
753 std::string setupScript = theSupervisorNode.
getNode(
"DAQSetupScript").
getValue();
756 std::string bootContent =
760 for(
auto& builder : info.processes[ARTDAQTableBase::ARTDAQAppType::EventBuilder])
761 label_to_proc_type_map_[builder.label] =
"EventBuilder";
762 for(
auto& logger : info.processes[ARTDAQTableBase::ARTDAQAppType::DataLogger])
763 label_to_proc_type_map_[logger.label] =
"DataLogger";
764 for(
auto& dispatcher : info.processes[ARTDAQTableBase::ARTDAQAppType::Dispatcher])
765 label_to_proc_type_map_[dispatcher.label] =
"Dispatcher";
766 for(
auto& rmanager : info.processes[ARTDAQTableBase::ARTDAQAppType::RoutingManager])
767 label_to_proc_type_map_[rmanager.label] =
"RoutingManager";
778 thread_progress_bar_.
step();
779 set_thread_message_(
"Writing Fhicl Files");
781 __GEN_COUT__ <<
"Building configuration directory" << __E__;
783 boost::system::error_code ignored;
788 for(
auto& reader : info.processes[ARTDAQTableBase::ARTDAQAppType::BoardReader])
790 symlink(ARTDAQTableBase::getFlatFHICLFilename(
791 ARTDAQTableBase::ARTDAQAppType::BoardReader, reader.label)
794 reader.label +
".fcl")
797 for(
auto& builder : info.processes[ARTDAQTableBase::ARTDAQAppType::EventBuilder])
799 symlink(ARTDAQTableBase::getFlatFHICLFilename(
800 ARTDAQTableBase::ARTDAQAppType::EventBuilder, builder.label)
803 builder.label +
".fcl")
806 for(
auto& logger : info.processes[ARTDAQTableBase::ARTDAQAppType::DataLogger])
808 symlink(ARTDAQTableBase::getFlatFHICLFilename(
809 ARTDAQTableBase::ARTDAQAppType::DataLogger, logger.label)
812 logger.label +
".fcl")
815 for(
auto& dispatcher : info.processes[ARTDAQTableBase::ARTDAQAppType::Dispatcher])
817 symlink(ARTDAQTableBase::getFlatFHICLFilename(
818 ARTDAQTableBase::ARTDAQAppType::Dispatcher, dispatcher.label)
821 dispatcher.label +
".fcl")
824 for(
auto& rmanager : info.processes[ARTDAQTableBase::ARTDAQAppType::RoutingManager])
826 symlink(ARTDAQTableBase::getFlatFHICLFilename(
827 ARTDAQTableBase::ARTDAQAppType::RoutingManager, rmanager.label)
830 rmanager.label +
".fcl")
834 thread_progress_bar_.
step();
839 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
841 if(daqinterface_state_ !=
"stopped" && daqinterface_state_ !=
"")
843 __GEN_SS__ <<
"Cannot configure DAQInterface because it is in the wrong state"
844 <<
" (" << daqinterface_state_ <<
" != stopped)!" << __E__;
848 if(daqinterface_ptr_ ==
nullptr)
850 __GEN_SS__ <<
"DAQInterface is not initialized. "
851 "Check earlier Python import/constructor errors (e.g. syntax) "
859 set_thread_message_(
"Calling setdaqcomps");
860 __GEN_COUT__ <<
"Calling setdaqcomps" << __E__;
862 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
864 __GEN_COUT__ <<
"Status before setdaqcomps: " << daqinterface_state_ << __E__;
866 PyObjectGuard pName1(PyUnicode_FromString(
"setdaqcomps"));
868 PyObjectGuard readerDict(PyDict_New());
869 for(
auto& reader : info.processes[ARTDAQTableBase::ARTDAQAppType::BoardReader])
872 label_to_proc_type_map_[reader.label] =
"BoardReader";
873 PyObjectGuard readerName(PyUnicode_FromString(reader.label.c_str()));
875 int list_size = reader.allowed_processors !=
"" ? 4 : 3;
877 PyObjectGuard readerData(PyList_New(list_size));
878 PyObject* readerHost = PyUnicode_FromString(reader.hostname.c_str());
879 PyObject* readerPort = PyUnicode_FromString(
"-1");
880 PyObject* readerSubsystem =
881 PyUnicode_FromString(std::to_string(reader.subsystem).c_str());
882 PyList_SetItem(readerData.get(), 0, readerHost);
883 PyList_SetItem(readerData.get(), 1, readerPort);
884 PyList_SetItem(readerData.get(), 2, readerSubsystem);
885 if(reader.allowed_processors !=
"")
887 PyObject* readerAllowedProcessors =
888 PyUnicode_FromString(reader.allowed_processors.c_str());
889 PyList_SetItem(readerData.get(), 3, readerAllowedProcessors);
891 PyDict_SetItem(readerDict.get(), readerName.get(), readerData.get());
893 PyObjectGuard res1(PyObject_CallMethodObjArgs(
894 daqinterface_ptr_, pName1.get(), readerDict.get(), NULL));
895 __COUT_MULTI_LBL__(0, captureStderrAndStdout_(
"setdaqcomps"),
"setdaqcomps");
897 if(checkPythonError(res1.get()))
899 std::string err_msg = capturePyErr(
"setdaqcomps");
900 __GEN_SS__ <<
"Error calling setdaqcomps: " << err_msg << __E__;
905 __GEN_COUT__ <<
"Status after setdaqcomps: " << daqinterface_state_ << __E__;
908 thread_progress_bar_.
step();
911 set_thread_message_(
"Calling do_boot");
912 __GEN_COUT_INFO__ <<
"Calling do_boot" << __E__;
913 std::string doBootOutput =
"";
915 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
917 __GEN_COUT__ <<
"Status before boot: " << daqinterface_state_ << __E__;
920 PyObjectGuard pNameBoot(PyUnicode_FromString(
"do_boot"));
921 PyObjectGuard pBootArgs(PyUnicode_FromString(
925 PyObjectGuard resBoot1(PyObject_CallMethodObjArgs(
926 daqinterface_ptr_, pNameBoot.get(), pBootArgs.get(), NULL));
928 doBootOutput = captureStderrAndStdout_(
"do_boot");
929 __COUT_MULTI_LBL__(0, doBootOutput,
"do_boot");
930 __GEN_COUT_INFO__ <<
"do_boot output captured (" << doBootOutput.size()
931 <<
" chars)" << __E__;
933 if(checkPythonError(resBoot1.get()))
937 std::string err1 = capturePyErr(
"do_boot");
939 __GEN_COUT_INFO__ <<
"Error on first boot attempt: " << err1
940 <<
". Recovering and retrying..." << __E__;
943 PyObjectGuard pNameRecover(PyUnicode_FromString(
"do_recover"));
944 PyObjectGuard resRecover(
945 PyObject_CallMethodObjArgs(daqinterface_ptr_, pNameRecover.get(), NULL));
946 __COUT_MULTI_LBL__(0, captureStderrAndStdout_(
"do_recover"),
"do_recover");
948 if(checkPythonError(resRecover.get()))
951 std::string errRec = capturePyErr(
"do_recover");
953 std::stringstream oss;
954 oss <<
"Error calling recover transition!!!! " << errRec;
955 if(doBootOutput.size() > OUT_ON_ERR_SIZE)
956 oss <<
"... last " << OUT_ON_ERR_SIZE
957 <<
" chars: " << doBootOutput.substr(doBootOutput.size() - 1000);
962 __GEN_SS__ << oss.str() << __E__;
967 thread_progress_bar_.
step();
968 set_thread_message_(
"Calling do_boot (retry)");
969 __GEN_COUT_INFO__ <<
"Calling do_boot again" << __E__;
972 PyObjectGuard resBoot2(PyObject_CallMethodObjArgs(
973 daqinterface_ptr_, pNameBoot.get(), pBootArgs.get(), NULL));
975 doBootOutput = captureStderrAndStdout_(
"do_boot (retry)");
976 __COUT_MULTI_LBL__(0, doBootOutput,
"do_boot (retry)");
978 if(checkPythonError(resBoot2.get()))
981 std::string err2 = capturePyErr(
"do_boot retry");
983 std::stringstream oss;
984 oss <<
"Error calling boot transition (2nd try): " << err2;
985 if(doBootOutput.size() > OUT_ON_ERR_SIZE)
986 oss <<
"... last " << OUT_ON_ERR_SIZE
987 <<
" chars: " << doBootOutput.substr(doBootOutput.size() - 1000);
991 __GEN_SS__ << oss.str() << __E__;
997 __GEN_COUT_INFO__ <<
"do_boot block complete, state=" << daqinterface_state_
999 if(daqinterface_state_ !=
"booted")
1001 std::cout <<
"Do boot output on error: \n" << doBootOutput << __E__;
1002 __GEN_SS__ <<
"DAQInterface boot transition failed! "
1003 <<
"Status after boot attempt: " << daqinterface_state_ << __E__;
1011 PyObject* pyCritErr =
1012 PyObject_GetAttrString(daqinterface_ptr_,
"last_critical_error");
1013 if(pyCritErr ==
nullptr)
1017 if(PyUnicode_Check(pyCritErr))
1021 const char* critErrCStr = PyUnicode_AsUTF8(pyCritErr);
1022 if(critErrCStr ==
nullptr)
1024 else if(critErrCStr[0] !=
'\0')
1025 ss <<
"\n\nCritical error: " << critErrCStr << __E__;
1027 Py_DECREF(pyCritErr);
1031 if(doBootOutput.size() > OUT_ON_ERR_SIZE)
1032 ss <<
"... last " << OUT_ON_ERR_SIZE
1033 <<
" characters: " << doBootOutput.substr(doBootOutput.size() - 1000);
1038 __GEN_COUT__ <<
"Status after boot: " << daqinterface_state_ << __E__;
1041 thread_progress_bar_.
step();
1044 set_thread_message_(
"Calling do_config");
1045 __GEN_COUT_INFO__ <<
"Calling do_config" << __E__;
1046 std::string doConfigOutput =
"";
1048 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1050 __GEN_COUT_INFO__ <<
"do_config python mutex acquired, state="
1051 << daqinterface_state_ << __E__;
1056 PyObjectGuard pName3(PyUnicode_FromString(
"do_config"));
1058 PyObjectGuard pArg(Py_BuildValue(
"[s]", FAKE_CONFIG_NAME));
1061 PyObjectGuard res3(PyObject_CallMethodObjArgs(
1062 daqinterface_ptr_, pName3.get(), pArg.get(), NULL));
1065 if(checkPythonError(res3.get()))
1068 std::string err = capturePyErr(
"do_config");
1071 doConfigOutput = captureStderrAndStdout_(
"do_config");
1073 __GEN_SS__ <<
"Error calling config transition: " << err << __E__;
1078 doConfigOutput = captureStderrAndStdout_(
"do_config");
1079 __COUT_MULTI_LBL__(0, doConfigOutput,
"do_config");
1083 PyObjectGuard strRes(PyObject_Str(res3.get()));
1084 const char* res_cstr =
"";
1087 res_cstr = PyUnicode_AsUTF8(strRes.get());
1090 __SUP_COUTT__ <<
"do_config result=" << (res_cstr ? res_cstr :
"N/A")
1095 if(daqinterface_state_ !=
"ready")
1097 __GEN_SS__ <<
"DAQInterface config transition failed!" << __E__
1098 <<
"Supervisor state: \"" << daqinterface_state_
1099 <<
"\" != \"ready\" " << __E__;
1100 auto doConfigOutput_recover_i =
1101 doConfigOutput.find(
"RECOVER transition underway");
1102 if(doConfigOutput_recover_i == std::string::npos)
1103 ss << doConfigOutput;
1104 else if(doConfigOutput_recover_i >
1106 ss <<
"... tail of " << OUT_ON_ERR_SIZE <<
" characters before recovery: "
1107 << doConfigOutput.substr(
1108 doConfigOutput_recover_i - OUT_ON_ERR_SIZE +
1109 std::string(
"RECOVER transition underway").size(),
1112 ss << doConfigOutput.substr(
1114 doConfigOutput_recover_i +
1115 std::string(
"RECOVER transition underway").size());
1118 __GEN_COUT__ <<
"Status after config: " << daqinterface_state_ << __E__;
1122 set_thread_message_(
"Configured");
1123 __GEN_COUT_INFO__ <<
"Configured." << __E__;
1126 catch(
const std::runtime_error& e)
1128 set_thread_message_(
"ERROR");
1129 __SS__ <<
"Error was caught while configuring: " << e.what() << __E__;
1130 __COUT_ERR__ <<
"\n" << ss.str();
1131 std::lock_guard<std::mutex> lock(thread_mutex_);
1132 thread_error_message_ = ss.str();
1136 set_thread_message_(
"ERROR");
1137 __SS__ <<
"Unknown error was caught while configuring. Please checked the logs."
1139 __COUT_ERR__ <<
"\n" << ss.str();
1141 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1143 std::lock_guard<std::mutex> lock(thread_mutex_);
1144 thread_error_message_ = ss.str();
1151 set_thread_message_(
"Halting");
1152 __SUP_COUT__ <<
"Halting..." << __E__;
1159 if(!stop_runner_(HALT_RUNNER_STOP_TIMEOUT_SECONDS))
1160 __SUP_COUT_WARN__ <<
"DAQInterface runner thread did not stop within "
1161 << HALT_RUNNER_STOP_TIMEOUT_SECONDS
1162 <<
" seconds; proceeding with the Halt transition." << __E__;
1167 std::unique_lock<std::recursive_timed_mutex> lk(
1168 daqinterface_pythonMutex_,
1169 std::chrono::seconds(HALT_PYTHON_MUTEX_TIMEOUT_SECONDS));
1172 __SUP_SS__ <<
"Could not acquire the DAQInterface Python mutex within "
1173 << HALT_PYTHON_MUTEX_TIMEOUT_SECONDS
1174 <<
" seconds during the Halt transition. A configure or "
1175 "heartbeat thread appears to be stuck inside a Python call."
1180 __SUP_COUT__ <<
"Status before halt: " << daqinterface_state_ << __E__;
1182 if(daqinterface_state_ ==
"running")
1185 PyObjectGuard pName(PyUnicode_FromString(
"do_stop_running"));
1187 PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), NULL));
1189 0, captureStderrAndStdout_(
"do_stop_running"),
"do_stop_running");
1191 if(res.get() == NULL)
1193 std::string err = capturePyErr();
1194 __SS__ <<
"Error calling DAQ Interface stop transition: " << err
1202 if(daqinterface_state_ ==
"stopped" || daqinterface_state_ ==
"")
1204 __SUP_COUT__ <<
"DAQInterface already stopped, skipping Shutdown command."
1209 PyObjectGuard pName(PyUnicode_FromString(
"do_command"));
1210 PyObjectGuard pArg(PyUnicode_FromString(
"Shutdown"));
1211 PyObjectGuard res(PyObject_CallMethodObjArgs(
1212 daqinterface_ptr_, pName.get(), pArg.get(), NULL));
1214 0, captureStderrAndStdout_(
"do_command Shutdown"),
"do_command Shutdown");
1216 if(checkPythonError(res.get()))
1218 std::string err = capturePyErr(
"do_command Shutdown");
1219 __SS__ <<
"Error calling DAQ Interface halt transition: " << err << __E__;
1225 __SUP_COUT__ <<
"Status after halt: " << daqinterface_state_ << __E__;
1228 __SUP_COUT__ <<
"Halted." << __E__;
1229 set_thread_message_(
"Halted");
1231 catch(
const std::runtime_error& e)
1233 const std::string transitionName =
"Halting";
1235 if(theStateMachine_.getProvenanceStateName() ==
1236 RunControlStateMachine::FAILED_STATE_NAME ||
1237 theStateMachine_.getProvenanceStateName() ==
1238 RunControlStateMachine::HALTED_STATE_NAME)
1240 __SUP_COUT_INFO__ <<
"Error was caught while halting (but ignoring because "
1241 "previous state was '"
1242 << RunControlStateMachine::FAILED_STATE_NAME
1243 <<
"'): " << e.what() << __E__;
1247 __SUP_SS__ <<
"Error was caught while " << transitionName <<
": " << e.what()
1249 __SUP_COUT_ERR__ <<
"\n" << ss.str();
1250 theStateMachine_.setErrorMessage(ss.str());
1251 throw toolbox::fsm::exception::Exception(
1252 "Transition Error" ,
1254 "ARTDAQSupervisorBase::transition" + transitionName ,
1262 const std::string transitionName =
"Halting";
1264 if(theStateMachine_.getProvenanceStateName() ==
1265 RunControlStateMachine::FAILED_STATE_NAME ||
1266 theStateMachine_.getProvenanceStateName() ==
1267 RunControlStateMachine::HALTED_STATE_NAME)
1269 __SUP_COUT_INFO__ <<
"Unknown error was caught while halting (but ignoring "
1270 "because previous state was '"
1271 << RunControlStateMachine::FAILED_STATE_NAME <<
"')." << __E__;
1275 __SUP_SS__ <<
"Unknown error was caught while " << transitionName
1276 <<
". Please checked the logs." << __E__;
1277 __SUP_COUT_ERR__ <<
"\n" << ss.str();
1278 theStateMachine_.setErrorMessage(ss.str());
1280 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1282 throw toolbox::fsm::exception::Exception(
1283 "Transition Error" ,
1285 "ARTDAQSupervisorBase::transition" + transitionName ,
1296 set_thread_message_(
"Initializing");
1297 __SUP_COUT__ <<
"Initializing..." << __E__;
1299 __SUP_COUT__ <<
"Initialized." << __E__;
1300 set_thread_message_(
"Initialized");
1302 catch(
const std::runtime_error& e)
1304 __SS__ <<
"Error was caught while Initializing: " << e.what() << __E__;
1309 __SS__ <<
"Unknown error was caught while Initializing. Please checked the logs."
1311 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1316 void ARTDAQSupervisor::transitionPausing(toolbox::Event::Reference )
1319 set_thread_message_(
"Pausing");
1320 __SUP_COUT__ <<
"Pausing..." << __E__;
1321 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1324 __SUP_COUT__ <<
"Status before pause: " << daqinterface_state_ << __E__;
1326 PyObjectGuard pName(PyUnicode_FromString(
"do_command"));
1327 PyObjectGuard pArg(PyUnicode_FromString(
"Pause"));
1329 PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), pArg.get(), NULL));
1331 0, captureStderrAndStdout_(
"do_command Pause"),
"do_command Pause");
1333 if(checkPythonError(res.get()))
1335 std::string err = capturePyErr(
"do_command Pause");
1336 __SS__ <<
"Error calling DAQ Interface Pause transition: " << err << __E__;
1341 __SUP_COUT__ <<
"Status after pause: " << daqinterface_state_ << __E__;
1343 __SUP_COUT__ <<
"Paused." << __E__;
1344 set_thread_message_(
"Paused");
1346 catch(
const std::runtime_error& e)
1348 __SS__ <<
"Error was caught while Pausing: " << e.what() << __E__;
1353 __SS__ <<
"Unknown error was caught while Pausing. Please checked the logs." << __E__;
1354 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1359 void ARTDAQSupervisor::transitionResuming(toolbox::Event::Reference )
1362 set_thread_message_(
"Resuming");
1363 __SUP_COUT__ <<
"Resuming..." << __E__;
1364 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1367 __SUP_COUT__ <<
"Status before resume: " << daqinterface_state_ << __E__;
1368 PyObjectGuard pName(PyUnicode_FromString(
"do_command"));
1369 PyObjectGuard pArg(PyUnicode_FromString(
"Resume"));
1371 PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), pArg.get(), NULL));
1373 0, captureStderrAndStdout_(
"do_command Resume"),
"do_command Resume");
1375 if(checkPythonError(res.get()))
1377 std::string err = capturePyErr(
"do_command Resume");
1378 __SS__ <<
"Error calling DAQ Interface Resume transition: " << err << __E__;
1383 __SUP_COUT__ <<
"Status after resume: " << daqinterface_state_ << __E__;
1384 __SUP_COUT__ <<
"Resumed." << __E__;
1385 set_thread_message_(
"Resumed");
1387 catch(
const std::runtime_error& e)
1389 __SS__ <<
"Error was caught while Resuming: " << e.what() << __E__;
1394 __SS__ <<
"Unknown error was caught while Resuming. Please checked the logs."
1396 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1401 void ARTDAQSupervisor::transitionStarting(toolbox::Event::Reference )
1404 __SUP_COUT__ <<
"transitionStarting" << __E__;
1413 const unsigned int startIteration =
1416 if(startIteration == 0)
1419 __SUP_COUT_INFO__ <<
"Step 0: idle, waiting for DTCs to SoftReset." << __E__;
1420 RunControlStateMachine::indicateSubsystemSyncStepWork();
1424 if(startIteration == 1 && RunControlStateMachine::getSubIterationIndex() == 0)
1427 thread_error_message_ =
"";
1428 thread_progress_bar_.resetProgressBar(0);
1429 last_thread_progress_update_ = time(0);
1431 std::thread(&ARTDAQSupervisor::startingThread,
this).detach();
1433 __SUP_COUT_INFO__ <<
"Step 1: artdaq starting thread launched." << __E__;
1438 RunControlStateMachine::indicateSubIterationWork();
1442 if(startIteration == 1)
1444 std::string errorMessage;
1446 std::lock_guard<std::mutex> lock(
1448 errorMessage = thread_error_message_;
1450 int progress = thread_progress_bar_.
read();
1451 __SUP_COUTV__(errorMessage);
1452 __SUP_COUTV__(progress);
1453 __SUP_COUTV__(thread_progress_bar_.
isComplete());
1455 if(errorMessage ==
"" && time(0) - last_thread_progress_update_ > 600)
1457 __SUP_SS__ <<
"There has been no update from the start thread for "
1458 << (time(0) - last_thread_progress_update_)
1459 <<
" seconds, assuming something is wrong and giving up! "
1460 <<
"Last progress received was " << progress << __E__;
1461 errorMessage = ss.str();
1464 if(errorMessage !=
"")
1466 __SUP_SS__ <<
"Error was caught in starting thread: " << errorMessage
1468 __SUP_COUT_ERR__ <<
"\n" << ss.str();
1470 theStateMachine_.setErrorMessage(ss.str());
1471 throw toolbox::fsm::exception::Exception(
1472 "Transition Error" ,
1474 "CoreSupervisorBase::transitionStarting" ,
1482 __SUP_COUT__ <<
"Step 1: artdaq not done yet..." << __E__;
1484 RunControlStateMachine::indicateSubIterationWork();
1486 if(last_thread_progress_read_ != progress)
1488 last_thread_progress_read_ = progress;
1489 last_thread_progress_update_ = time(0);
1498 __SUP_COUT_INFO__ <<
"Step 1: artdaq starting transition completed!" << __E__;
1499 __SUP_COUTV__(getProcessInfo_());
1500 RunControlStateMachine::indicateSubsystemSyncStepWork();
1510 catch(
const std::runtime_error& e)
1512 __SS__ <<
"Error was caught while Starting: " << e.what() << __E__;
1517 __SS__ <<
"Unknown error was caught while Starting. Please checked the logs."
1519 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1524 void ARTDAQSupervisor::startingThread()
1527 std::string uid = theConfigurationManager_
1528 ->
getNode(ConfigurationManager::XDAQ_APPLICATION_TABLE_NAME +
1529 "/" + CorePropertySupervisorBase::getSupervisorUID() +
1530 "/" +
"LinkToSupervisorTable")
1533 __COUT__ <<
"Supervisor uid is " << uid <<
", getting supervisor table node" << __E__;
1534 const std::string mfSubject_ = supervisorClassNoNamespace_ +
"-" + uid;
1535 __GEN_COUT__ <<
"Starting..." << __E__;
1536 set_thread_message_(
"Starting");
1538 thread_progress_bar_.
step();
1541 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1543 __GEN_COUT__ <<
"Status before start: " << daqinterface_state_ << __E__;
1544 auto runNumber = SOAPUtilities::translate(theStateMachine_.getCurrentMessage())
1546 .getValue(
"RunNumber");
1548 thread_progress_bar_.
step();
1550 __GEN_COUT_INFO__ <<
"Calling do_start_running" << __E__;
1551 PyObjectGuard pName(PyUnicode_FromString(
"do_start_running"));
1552 int run_number = std::stoi(runNumber);
1553 PyObjectGuard pStateArgs(PyLong_FromLong(run_number));
1554 PyObjectGuard res(PyObject_CallMethodObjArgs(
1555 daqinterface_ptr_, pName.get(), pStateArgs.get(), NULL));
1556 std::string doStartOutput;
1558 thread_progress_bar_.
step();
1560 if(checkPythonError(res.get()))
1562 std::string err = capturePyErr(
"do_start_running");
1563 doStartOutput = captureStderrAndStdout_(
"do_start_running");
1564 __SS__ <<
"Error calling start transition: " << err << __E__;
1565 if(doStartOutput.size() > OUT_ON_ERR_SIZE)
1566 ss <<
"... last " << OUT_ON_ERR_SIZE <<
" characters: "
1567 << doStartOutput.substr(doStartOutput.size() - OUT_ON_ERR_SIZE);
1569 ss << doStartOutput;
1573 doStartOutput = captureStderrAndStdout_(
"do_start_running");
1574 __COUT_MULTI_LBL__(0, doStartOutput,
"do_start_running");
1577 thread_progress_bar_.
step();
1579 __GEN_COUT__ <<
"Status after start: " << daqinterface_state_ << __E__;
1580 if(daqinterface_state_ !=
"running")
1582 __SS__ <<
"DAQInterface start transition failed!" << __E__
1583 <<
"DAQInterface state: \"" << daqinterface_state_
1584 <<
"\" != \"running\" " << __E__;
1585 if(doStartOutput.size() > OUT_ON_ERR_SIZE)
1586 ss <<
"... last " << OUT_ON_ERR_SIZE <<
" characters: "
1587 << doStartOutput.substr(doStartOutput.size() - OUT_ON_ERR_SIZE);
1589 ss << doStartOutput;
1593 thread_progress_bar_.
step();
1596 set_thread_message_(
"Started");
1597 thread_progress_bar_.
step();
1599 __GEN_COUT_INFO__ <<
"Started." << __E__;
1603 catch(
const std::runtime_error& e)
1605 __SS__ <<
"Error was caught while Starting: " << e.what() << __E__;
1606 __COUT_ERR__ <<
"\n" << ss.str();
1607 std::lock_guard<std::mutex> lock(thread_mutex_);
1608 thread_error_message_ = ss.str();
1612 __SS__ <<
"Unknown error was caught while Starting. Please checked the logs."
1614 __COUT_ERR__ <<
"\n" << ss.str();
1616 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1618 std::lock_guard<std::mutex> lock(thread_mutex_);
1619 thread_error_message_ = ss.str();
1623 void ARTDAQSupervisor::transitionStopping(toolbox::Event::Reference )
1626 __SUP_COUT__ <<
"Stopping..." << __E__;
1627 set_thread_message_(
"Stopping");
1628 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1630 __SUP_COUT__ <<
"Status before stop: " << daqinterface_state_ << __E__;
1631 PyObjectGuard pName(PyUnicode_FromString(
"do_stop_running"));
1632 PyObjectGuard res(PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), NULL));
1633 __COUT_MULTI_LBL__(0, captureStderrAndStdout_(
"do_stop_running"),
"do_stop_running");
1635 if(checkPythonError(res.get()))
1637 std::string err = capturePyErr(
"do_stop_running");
1638 __SS__ <<
"Error calling DAQ Interface stop transition: " << err << __E__;
1642 __SUP_COUT__ <<
"Status after stop: " << daqinterface_state_ << __E__;
1643 __SUP_COUT__ <<
"Stopped." << __E__;
1644 set_thread_message_(
"Stopped");
1646 catch(
const std::runtime_error& e)
1648 __SS__ <<
"Error was caught while Stopping: " << e.what() << __E__;
1653 __SS__ <<
"Unknown error was caught while Stopping. Please checked the logs."
1655 artdaq::ExceptionHandler(artdaq::ExceptionHandlerRethrow::no, ss.str());
1660 void ots::ARTDAQSupervisor::enteringError(toolbox::Event::Reference )
1662 __SUP_COUT__ <<
"Entering error recovery state" << __E__;
1663 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1665 __SUP_COUT__ <<
"Status before error: " << daqinterface_state_ << __E__;
1667 PyObjectGuard pName(PyUnicode_FromString(
"do_recover"));
1668 PyObjectGuard res(PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), NULL));
1669 __COUT_MULTI_LBL__(0, captureStderrAndStdout_(
"do_recover"),
"do_recover");
1671 if(checkPythonError(res.get()))
1673 std::string err = capturePyErr(
"do_recover");
1675 __SUP_COUT_WARN__ <<
"Error calling DAQ Interface recover transition: " << err
1681 __SUP_COUT__ <<
"Status after error: " << daqinterface_state_ << __E__;
1682 __SUP_COUT__ <<
"EnteringError DONE." << __E__;
1686 std::vector<SupervisorInfo::SubappInfo> ots::ARTDAQSupervisor::getSubappInfo(
void)
1688 auto apps = getAndParseProcessInfo_();
1690 std::map<int, SupervisorInfo::SubappInfo> subapp_infos;
1691 for(
auto& app : apps)
1695 info.
name = app.label;
1696 info.detail =
"Rank " + std::to_string(app.rank) +
", subsystem " +
1697 std::to_string(app.subsystem);
1698 info.lastStatusTime = time(0);
1699 info.progress = 100;
1700 info.status = artdaqStateToOtsState(app.state);
1701 info.url =
"http://" + app.host +
":" + std::to_string(app.port) +
"/RPC2";
1702 info.class_name =
"ARTDAQ " + labelToProcType_(app.label);
1704 subapp_infos[app.rank] = info;
1707 std::vector<SupervisorInfo::SubappInfo> output;
1708 for(
auto& [rank, info] : subapp_infos)
1710 output.push_back(info);
1719 bool ots::ARTDAQSupervisor::checkPythonError(PyObject* result)
1721 if(result == NULL || PyErr_Occurred())
1737 std::string ots::ARTDAQSupervisor::capturePyErr(std::string label ,
1738 std::string* summaryOut )
1740 std::string err_msg =
"Unknown Python Error";
1741 PyObject * pType, *pValue, *pTraceback;
1742 PyErr_Fetch(&pType, &pValue, &pTraceback);
1743 PyErr_NormalizeException(&pType, &pValue, &pTraceback);
1746 *summaryOut = err_msg;
1752 std::string summary = ((PyTypeObject*)pType)->tp_name;
1755 PyObjectGuard pStr(PyObject_Str(pValue));
1756 const char* valueCStr =
1757 pStr.get() ? PyUnicode_AsUTF8(pStr.get()) : nullptr;
1760 std::string value(valueCStr);
1761 size_t b = value.find_first_not_of(
" \t\r\n");
1762 size_t e = value.find_last_not_of(
" \t\r\n");
1763 if(b != std::string::npos)
1764 summary +=
": " + value.substr(b, e - b + 1);
1769 *summaryOut = summary;
1773 PyObjectGuard traceback_module(PyImport_ImportModule(
"traceback"));
1774 if(traceback_module.get() != NULL)
1776 PyObjectGuard format_exception(
1777 PyObject_GetAttrString(traceback_module.get(),
"format_exception"));
1778 if(format_exception.get() != NULL)
1780 PyObjectGuard formatted(
1781 PyObject_CallFunctionObjArgs(format_exception.get(),
1783 pValue ? pValue : Py_None,
1784 pTraceback ? pTraceback : Py_None,
1786 if(formatted.get() != NULL)
1789 PyObjectGuard empty_string(PyUnicode_FromString(
""));
1790 PyObjectGuard joined(
1791 PyUnicode_Join(empty_string.get(), formatted.get()));
1792 if(joined.get() != NULL)
1794 const char* traceback_cstr = PyUnicode_AsUTF8(joined.get());
1796 err_msg = traceback_cstr;
1803 if(err_msg ==
"Unknown Python Error" && pValue != NULL)
1805 PyObjectGuard pStr(PyObject_Str(pValue));
1806 if(pStr.get() != NULL)
1808 const char* error_cstr = PyUnicode_AsUTF8(pStr.get());
1810 err_msg = error_cstr;
1817 Py_XDECREF(pTraceback);
1821 err_msg = label +
":\n" + err_msg;
1827 std::string ots::ARTDAQSupervisor::captureStderrAndStdout_(std::string label )
1832 if(PyErr_Occurred())
1837 std::string outString =
"";
1838 PyObjectGuard out(PyObject_CallMethod(stringIO_out_,
"getvalue", NULL));
1840 if(checkPythonError(out.get()))
1843 capturePyErr(
"captureStderrAndStdout getvalue");
1847 const char* text = PyUnicode_AsUTF8(out.get());
1849 return text ? text :
"";
1852 void ots::ARTDAQSupervisor::getDAQState_()
1854 __SUP_COUTS__(50) <<
"Getting DAQInterface python lock" << __E__;
1855 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1856 __SUP_COUTS__(50) <<
"Have DAQInterface python lock" << __E__;
1858 if(daqinterface_ptr_ == NULL)
1860 daqinterface_state_ =
"";
1861 __SUP_COUT_WARN__ <<
"daqinterface_ptr_ is not initialized!" << __E__;
1866 PyObjectGuard pName(PyUnicode_FromString(
"state"));
1867 PyObjectGuard pArg(PyUnicode_FromString(
"DAQInterface"));
1878 PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), pArg.get(), NULL));
1880 if(checkPythonError(res.get()))
1885 std::string err_msg = capturePyErr(
"state");
1887 std::ostringstream ss;
1888 ss <<
"Attempt n " << tries
1889 <<
". Error calling 'state'. Python Exception: " << err_msg;
1893 __COUT_ERR__ << ss.str() << __E__;
1894 daqinterface_state_ =
"ERROR";
1898 __COUT__ << ss.str() << __E__;
1907 PyObjectGuard strRes(PyObject_Str(res.get()));
1910 daqinterface_state_ = std::string(PyUnicode_AsUTF8(strRes.get()));
1915 daqinterface_state_ =
"UNKNOWN";
1918 __SUP_COUTS__(20) <<
"getDAQState_ state=" << daqinterface_state_ << __E__;
1926 std::string ots::ARTDAQSupervisor::getProcessInfo_(
void)
1928 __SUP_COUTS__(50) <<
"Getting DAQInterface state lock" << __E__;
1929 std::lock_guard<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
1930 __SUP_COUTS__(50) <<
"Have DAQInterface state lock" << __E__;
1932 if(daqinterface_ptr_ ==
nullptr)
1937 PyObjectGuard pName(PyUnicode_FromString(
"artdaq_process_info"));
1938 PyObjectGuard pArg(PyUnicode_FromString(
"DAQInterface"));
1939 PyObjectGuard pArg2(PyBool_FromLong(
true));
1940 PyObjectGuard res(PyObject_CallMethodObjArgs(
1941 daqinterface_ptr_, pName.get(), pArg.get(), pArg2.get(), NULL));
1943 if(checkPythonError(res.get()))
1945 std::string err = capturePyErr(
"artdaq_process_info");
1946 __SS__ <<
"Error calling artdaq_process_info function: " << err << __E__;
1951 std::lock_guard<std::mutex> lock(daqinterface_statusMutex_);
1952 daqinterface_status_ = std::string(PyUnicode_AsUTF8(res.get()));
1953 return daqinterface_status_;
1956 std::string ots::ARTDAQSupervisor::artdaqStateToOtsState(std::string state)
1958 if(state ==
"nonexistent" || state ==
"nonexistant")
1959 return RunControlStateMachine::INITIAL_STATE_NAME;
1960 if(state ==
"Ready")
1961 return "Configured";
1962 if(state ==
"Running")
1963 return RunControlStateMachine::RUNNING_STATE_NAME;
1964 if(state ==
"Paused")
1965 return RunControlStateMachine::PAUSED_STATE_NAME;
1966 if(state ==
"Stopped")
1967 return RunControlStateMachine::HALTED_STATE_NAME;
1969 TLOG(TLVL_WARNING) <<
"Unrecognized state name " << state;
1970 return RunControlStateMachine::FAILED_STATE_NAME;
1973 std::string ots::ARTDAQSupervisor::labelToProcType_(std::string label)
1975 if(label_to_proc_type_map_.count(label))
1977 return label_to_proc_type_map_[label];
1984 std::list<ots::ARTDAQSupervisor::DAQInterfaceProcessInfo>
1985 ots::ARTDAQSupervisor::getAndParseProcessInfo_()
1987 std::list<ots::ARTDAQSupervisor::DAQInterfaceProcessInfo> output;
1992 std::unique_lock<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_,
1996 __COUTS__(50) <<
"Do not have python lock." << __E__;
1997 std::lock_guard<std::mutex> lock(daqinterface_statusMutex_);
1998 info = daqinterface_status_;
2002 __COUTS__(50) <<
"Have python lock!" << __E__;
2003 info = getProcessInfo_();
2005 __COUTVS__(20, info);
2007 auto procs = tokenize_(info);
2016 std::regex re(
"(.*?) at ([^:]*):(\\d+) \\(subsystem (\\d+), rank (\\d+)\\): (.*)");
2018 for(
auto& proc : procs)
2021 if(std::regex_match(proc, match, re))
2023 DAQInterfaceProcessInfo info;
2025 info.label = match[1];
2026 info.host = match[2];
2027 info.port = std::stoi(match[3]);
2028 info.subsystem = std::stoi(match[4]);
2029 info.rank = std::stoi(match[5]);
2030 info.state = match[6];
2032 output.push_back(info);
2040 std::unique_ptr<artdaq::CommanderInterface>>>
2041 ots::ARTDAQSupervisor::makeCommandersFromProcessInfo()
2044 std::pair<DAQInterfaceProcessInfo, std::unique_ptr<artdaq::CommanderInterface>>>
2046 auto infos = getAndParseProcessInfo_();
2048 for(
auto& info : infos)
2050 artdaq::Commandable cm;
2051 fhicl::ParameterSet ps;
2053 ps.put<std::string>(
"commanderPluginType",
"xmlrpc");
2054 ps.put<
int>(
"id", info.port);
2055 ps.put<std::string>(
"server_url", info.host);
2057 output.emplace_back(std::make_pair<DAQInterfaceProcessInfo,
2058 std::unique_ptr<artdaq::CommanderInterface>>(
2059 std::move(info), artdaq::MakeCommanderPlugin(ps, cm)));
2074 std::set<std::string> ots::ARTDAQSupervisor::getConfiguredArtdaqHosts(
void)
2076 std::set<std::string> hosts;
2081 supervisorNode,
false ,
false );
2082 for(
const auto& typeProcs : info.processes)
2083 for(
const auto& proc : typeProcs.second)
2084 if(proc.status && !proc.hostname.empty())
2085 hosts.insert(proc.hostname);
2087 catch(
const std::exception& e)
2089 __SUP_COUT_ERR__ <<
"Failed to extract configured artdaq hosts: " << e.what()
2095 <<
"Failed to extract configured artdaq hosts (unknown exception)." << __E__;
2103 std::list<std::string> ots::ARTDAQSupervisor::tokenize_(std::string
const& input)
2106 std::list<std::string> output;
2108 while(pos != std::string::npos && pos < input.size())
2110 auto newpos = input.find(
'\n', pos);
2111 if(newpos != std::string::npos)
2113 output.emplace_back(input, pos, newpos - pos);
2119 output.emplace_back(input, pos);
2140 void ots::ARTDAQSupervisor::daqinterfaceRunner_(std::shared_ptr<RunnerControl> control)
2149 TLOG(TLVL_TRACE) <<
"Runner thread starting";
2150 unsigned int consecutiveStatusFailures = 0;
2151 while(control->running)
2153 if(daqinterface_ptr_ != NULL)
2155 std::unique_lock<std::recursive_timed_mutex> lk(daqinterface_pythonMutex_);
2157 std::string state_before = daqinterface_state_;
2159 __SUP_COUTS__(2) <<
"Runner state_before=" << state_before
2160 <<
" state now=" << daqinterface_state_
2161 <<
" ?= running, ready, or booted" << __E__;
2163 if(daqinterface_state_ ==
"running" || daqinterface_state_ ==
"ready" ||
2164 daqinterface_state_ ==
"booted")
2170 PyObject* resRaw =
nullptr;
2173 TLOG(TLVL_TRACE) <<
"Calling DAQInterface::check_proc_exceptions";
2174 PyObjectGuard pName(PyUnicode_FromString(
"check_proc_exceptions"));
2176 PyObject_CallMethodObjArgs(daqinterface_ptr_, pName.get(), NULL);
2178 <<
"Done with DAQInterface::check_proc_exceptions call";
2180 catch(cet::exception& ex)
2182 control->running =
false;
2183 std::string err = capturePyErr(
"check_proc_exceptions");
2184 __SS__ <<
"An cet::exception occurred while calling "
2185 "check_proc_exceptions function "
2186 << ex.explain_self() <<
": " << err << __E__;
2189 catch(std::exception& ex)
2191 control->running =
false;
2192 std::string err = capturePyErr(
"check_proc_exceptions");
2193 __SS__ <<
"An std::exception occurred while calling "
2194 "check_proc_exceptions function: "
2195 << ex.what() <<
"\n\n"
2201 control->running =
false;
2202 std::string err = capturePyErr(
"check_proc_exceptions");
2203 __SS__ <<
"An unknown Error occurred while calling "
2204 "check_proc_exceptions function: "
2209 PyObjectGuard res(resRaw);
2210 std::string pollOutput = captureStderrAndStdout_(
"check_proc_exceptions");
2212 if(res.get() == NULL)
2218 control->running =
false;
2219 std::string summary;
2220 std::string traceback =
2221 capturePyErr(
"check_proc_exceptions", &summary);
2228 <<
"check_proc_exceptions raised."
2229 << (pollOutput.size() ?
"\nCaptured Python stdout:\n" + pollOutput
2231 <<
"\nPython traceback:\n"
2232 << traceback << __E__;
2234 <<
"DAQInterface reported a problem with the artdaq processes: "
2235 << summary << (pollOutput.size() ?
"\n\n" + pollOutput :
"")
2236 <<
"\n\nSee the preceding DAQInterface_partition_" << partition_
2237 <<
" error messages in the Console (or the ARTDAQSupervisor "
2238 "log) for the affected process and its logfile."
2243 if(PyObject_IsTrue(res.get()) == 1)
2245 consecutiveStatusFailures = 0;
2246 __COUT_MULTI_LBL__(1, pollOutput,
"check_proc_exceptions");
2254 ++consecutiveStatusFailures;
2256 <<
"DAQInterface XML-RPC status poll reported a problem ("
2257 << consecutiveStatusFailures <<
" of "
2258 << RUNNER_MAX_CONSECUTIVE_STATUS_FAILURES
2259 <<
" consecutive allowed):\n"
2260 << pollOutput << __E__;
2261 if(consecutiveStatusFailures >=
2262 RUNNER_MAX_CONSECUTIVE_STATUS_FAILURES)
2264 control->running =
false;
2265 __SS__ <<
"artdaq process status could not be confirmed for "
2266 << consecutiveStatusFailures
2267 <<
" consecutive polls. Last DAQInterface output:\n"
2268 << pollOutput << __E__;
2275 if(daqinterface_state_ != state_before)
2277 control->running =
false;
2278 __SS__ <<
"DAQInterface state unexpectedly changed from "
2279 << state_before <<
" to " << daqinterface_state_
2280 <<
". Check supervisor log file for more info!" << __E__;
2288 __SUP_COUT__ <<
"daqinterface_ptr_ is null" << __E__;
2293 control->running =
false;
2294 TLOG(TLVL_TRACE) <<
"Runner thread complete";
2298 __SS__ <<
"An error occurred in "
2299 "start_runner_/daqinterfaceRunner_ thread "
2305 catch(
const std::runtime_error& e)
2307 ss <<
"Here is the error: " << e.what() << __E__;
2311 ss <<
"Unexpected error!" << __E__;
2313 __COUT_ERR__ << ss.str();
2316 std::lock_guard<std::mutex> lock(
2318 thread_error_message_ = ss.str();
2321 theStateMachine_.setErrorMessage(ss.str());
2323 sendAsyncExceptionToGateway(
2341 bool ots::ARTDAQSupervisor::stop_runner_(
unsigned int timeoutSeconds)
2343 if(!runner_control_)
2348 std::shared_ptr<RunnerControl> control = std::move(runner_control_);
2349 std::unique_ptr<std::thread> thread = std::move(runner_thread_);
2351 control->running =
false;
2353 const auto deadline =
2354 std::chrono::steady_clock::now() + std::chrono::seconds(timeoutSeconds);
2355 while(!control->exited && std::chrono::steady_clock::now() < deadline)
2356 std::this_thread::sleep_for(std::chrono::milliseconds(50));
2358 if(!control->exited)
2360 __SUP_COUT_WARN__ <<
"DAQInterface runner thread did not exit within "
2362 <<
" seconds; it is likely blocked inside a Python call "
2363 "holding the interpreter mutex. Abandoning the thread "
2364 "rather than hanging this transition indefinitely."
2366 if(thread && thread->joinable())
2370 runner_abandoned_ =
true;
2374 if(thread && thread->joinable())
2380 void ots::ARTDAQSupervisor::start_runner_()
2386 runner_control_ = std::make_shared<RunnerControl>();
2387 runner_thread_ = std::make_unique<std::thread>([
this, control = runner_control_]() {
2391 struct RunnerExitFlag
2393 std::atomic<bool>& flag;
2394 ~RunnerExitFlag() { flag =
true; }
2395 } runnerExitFlag{control->exited};
2396 daqinterfaceRunner_(control);
2401 std::string ARTDAQSupervisor::getServiceDataFilePath()
const
2403 return StringMacros::getPersistentSystemVariablesFilePath();
2407 void ARTDAQSupervisor::initArtdaqSystemVariables()
2409 loadArtdaqSystemVariables();
2411 auto& ns = StringMacros::systemVariables_[ARTDAQ_SYSVAR_NAMESPACE];
2412 __SUP_COUT__ <<
"Artdaq system variables initialized: "
2417 void ARTDAQSupervisor::loadArtdaqSystemVariables()
2420 __SUP_COUT__ <<
"Loaded artdaq system variables from " << getServiceDataFilePath()
2423 __SUP_COUT__ <<
"No persisted artdaq system variables file found at "
2424 << getServiceDataFilePath() << __E__;
2428 void ARTDAQSupervisor::saveArtdaqSystemVariables()
2430 std::string filePath = getServiceDataFilePath();
2431 std::ofstream file(filePath);
2434 __SUP_SS__ <<
"Failed to open file for writing artdaq system variables: "
2435 << filePath << __E__;
2439 for(
auto& [key, value] : StringMacros::systemVariables_[ARTDAQ_SYSVAR_NAMESPACE])
2440 file << key <<
"=" << value <<
"\n";
2442 __SUP_COUT__ <<
"Saved artdaq system variables to " << filePath << __E__;
2446 void ARTDAQSupervisor::forceSupervisorPropertyValues(
void)
2448 CorePropertySupervisorBase::addSupervisorProperty(
2449 CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
2450 "getSystemVariables | getJsonDocuments");
2455 cgicc::Cgicc& cgiIn,
2460 __SUP_COUT__ <<
"ARTDAQSupervisor request: " << requestType << __E__;
2462 if(requestType ==
"getSystemVariables")
2464 for(
auto& [key, value] : StringMacros::systemVariables_[ARTDAQ_SYSVAR_NAMESPACE])
2465 xmlOut.addTextElementToData(
"artdaq_" + key, value);
2467 else if(requestType ==
"setSystemVariable")
2474 xmlOut.addTextElementToData(
"Error",
"Variable key must not be empty.");
2478 if(!std::isalnum(c) && c !=
'_')
2480 xmlOut.addTextElementToData(
2482 "Variable key must contain only alphanumeric characters and "
2487 StringMacros::systemVariables_[ARTDAQ_SYSVAR_NAMESPACE][key] = value;
2488 saveArtdaqSystemVariables();
2490 __SUP_COUT__ <<
"Set artdaq system variable " << key <<
" = " << value << __E__;
2491 xmlOut.addTextElementToData(
"Success",
"Variable '" + key +
"' set.");
2493 else if(requestType ==
"getJsonDocuments")
2495 auto* ifc = ConfigurationInterface::getInstance();
2497 std::set<std::string> allTableNames = ifc->getAllTableNames();
2499 for(
const auto& tableName : allTableNames)
2501 if(tableName.find(TableBase::JSON_DOC_PREPEND) != 0)
2504 std::string docName = tableName.substr(TableBase::JSON_DOC_PREPEND.size());
2507 std::set<TableVersion> versions = ifc->getVersions(&tmpTable);
2509 std::string versionList;
2510 for(
const auto& v : versions)
2512 if(!versionList.empty())
2514 versionList += v.toString();
2517 xmlOut.addTextElementToData(
"jsonDoc_name", docName);
2518 xmlOut.addTextElementToData(
"jsonDoc_versions", versionList);
2523 __SUP_SS__ <<
"Unknown request type '" << requestType <<
"' for ARTDAQSupervisor."
2525 __SUP_COUT__ << ss.str();
2526 xmlOut.addTextElementToData(
"Error", ss.str());
2529 catch(
const std::runtime_error& e)
2531 __SUP_SS__ <<
"Error handling request '" << requestType <<
"': " << e.what() << __E__;
2532 __SUP_COUT_ERR__ << ss.str();
2533 xmlOut.addTextElementToData(
"Error", ss.str());
2537 __SUP_SS__ <<
"Unknown error handling request '" << requestType <<
"'." << __E__;
2538 __SUP_COUT_ERR__ << ss.str();
2539 xmlOut.addTextElementToData(
"Error", ss.str());
void request(const std::string &requestType, cgicc::Cgicc &cgiIn, HttpXmlDocument &xmlOut, const WebUsers::RequestUserInfo &userInfo) override
virtual void transitionHalting(toolbox::Event::Reference event) override
virtual void transitionInitializing(toolbox::Event::Reference event) override
static const std::string ARTDAQ_FCL_PATH
Tree-path rule is, if the last link in the path is a group link with a specified group ID,...
static std::string getBootFileContentFromInfo(const ARTDAQInfo &info, const std::string &setupScript, int debugLevel)
static std::string postData(cgicc::Cgicc &cgi, const std::string &needle)
ConfigurationTree getNode(const std::string &nodeString, bool doNotThrowOnBrokenUIDLinks=false) const
"root/parent/parent/"
ConfigurationTree getNode(const std::string &nodeName, bool doNotThrowOnBrokenUIDLinks=false) const
navigating between nodes
const std::string & getValueAsString(bool returnLinkTableValue=false) const
void getValue(T &value) const
ITRACEController * theTRACEController_
only define for an app that receives a command
bool isComplete()
get functions
int read()
if stepsToComplete==0, then define any progress as 50%, thread safe
void complete()
declare complete, thread safe
bool isFirstIteration(void) const
true on the very first call of a transition (all indices at their starting value)
unsigned int getSubsystemSyncStepIndex(void) const
defines used also by OtsConfigurationWizardSupervisor
void INIT_MF(const char *name)
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
setToString ~
static bool loadPersistentSystemVariables(void)
loads persisted 'artdaq' namespace systemVariables_; returns false if no file found
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
std::string name
Also key in map.