20#include <framework/core/Environment.h>
21#include <framework/core/DataFlowVisualization.h>
22#include <framework/core/MetadataService.h>
23#include <framework/core/Module.h>
24#include <framework/core/ModuleManager.h>
25#include <framework/core/RandomNumbers.h>
26#include <framework/logging/Logger.h>
27#include <framework/logging/LogConfig.h>
28#include <framework/logging/LogSystem.h>
29#include <framework/utilities/FileSystem.h>
32#include <boost/program_options.hpp>
33#include <boost/algorithm/string/predicate.hpp>
47#include <valgrind/valgrind.h>
53namespace prog = boost::program_options;
57 void checkPythonStatus(PyConfig& config, PyStatus& status)
59 if (PyStatus_Exception(status)) {
60 PyConfig_Clear(&config);
61 Py_ExitStatusException(status);
66int main(
int argc,
char* argv[])
71 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
72 B2FATAL(
"Cannot remove SIGPIPE signal handler");
82 const char* belle2SubDir = getenv(
"BELLE2_SUBDIR");
83 std::filesystem::path libPath =
"lib";
84 libPath /= belle2SubDir;
86 string runModuleIOVisualization(
"");
87 vector<string> arguments;
95 prog::options_description
generic(
"Generic options (to be used instead of steering file)");
97 (
"help,h",
"Print this help")
98 (
"version,v",
"Print long and verbose version string")
99 (
"version-short",
"Print short version string")
100 (
"info",
"Print information about basf2")
101 (
"license",
"Print the short version of the basf2 license")
102 (
"modules,m", prog::value<string>()->implicit_value(
""),
103 "Print a list of all available modules (can be limited to a given package), or give detailed information on a specific module given as an argument (case sensitive).")
106 prog::options_description config(
"Configuration");
108 (
"steering", prog::value<string>(),
"The python steering file to run.")
109 (
"arg", prog::value<vector<string> >(&arguments),
"Additional arguments to be passed to the steering file")
110 (
"log_level,l", prog::value<string>(),
111 "Set global log level (one of DEBUG, INFO, RESULT, WARNING, or ERROR). Takes precedence over set_log_level() in steering file.")
112 (
"package_log_level", prog::value<vector<string> >(),
113 "Set package log level. Can be specified multiple times to use more than one package. (Examples: 'klm:INFO or cdc:DEBUG:10') ")
114 (
"random-seed", prog::value<string>(),
115 "Set the default initial seed for the random number generator. "
116 "This does not take precedence over calls to set_random_seed() in the steering file, but just changes the default. "
117 "If no seed is set via either of these mechanisms, the initial seed will be taken from the system's entropy pool.")
118 (
"debug_level,d", prog::value<unsigned int>(),
"Set default debug level. Also sets the log level to DEBUG.")
119 (
"events,n", prog::value<unsigned int>(),
"Override number of events for EventInfoSetter; otherwise set maximum number of events.")
120 (
"run", prog::value<int>(),
"Override run for EventInfoSetter, must be used with -n and --experiment")
121 (
"experiment", prog::value<int>(),
"Override experiment for EventInfoSetter, must be used with -n and --run")
122 (
"skip-events", prog::value<unsigned int>(),
123 "Override skipNEvents for EventInfoSetter and RootInput. Skips this many events before starting.")
124 (
"input,i", prog::value<vector<string> >(),
125 "Override name of input file for (Seq)RootInput. Can be specified multiple times to use more than one file. For RootInput, wildcards (as in *.root or [1-3].root) can be used, but need to be escaped with \\ or by quoting the argument to avoid expansion by the shell.")
126 (
"sequence,S", prog::value<vector<string> >(),
127 "Override the number sequence (e.g. 23:42,101) defining the entries (starting from 0) which are processed by RootInput."
128 "Must be specified exactly once for each file to be opened."
129 "This means one sequence per input file AFTER wildcard expansion."
130 "The first event has the number 0.")
131 (
"output,o", prog::value<string>(),
132 "Override name of output file for (Seq)RootOutput. In case multiple modules are present in the path, only the first will be affected.")
133 (
"processes,p", prog::value<int>(),
"Override number of worker processes (>=1 enables, 0 disables parallel processing)");
135 prog::options_description advanced(
"Advanced Options");
136 advanced.add_options()
137 (
"module-io", prog::value<string>(),
138 "Create diagram of inputs and outputs for a single module, saved as ModuleName.dot. To create a PostScript file, use e.g. 'dot ModuleName.dot -Tps -o out.ps'.")
139 (
"visualize-dataflow",
"Generate data flow diagram (dataflow.dot) for the executed steering file.")
141 "Disable collection of statistics during event processing. Useful for very high-rate applications, but produces empty table with 'print(statistics)'.")
143 "Read steering file, but do not start any event processing when process(path) is called. Prints information on input/output files that would be used during normal execution.")
144 (
"dump-path", prog::value<string>(),
145 "Read steering file, but do not actually start any event processing. The module path the steering file would execute is instead pickled (serialized) into the given file.")
146 (
"execute-path", prog::value<string>(),
147 "Do not read any provided steering file, instead execute the pickled (serialized) path from the given file.")
149 "Use ZMQ for multiprocessing instead of a RingBuffer. This has many implications and should only be used by experts.")
150 (
"job-information", prog::value<string>(),
151 "Create json file with metadata of output files and basf2 execution status.")
152 (
"realm", prog::value<string>(),
153 "Set the realm of the basf2 execution (online or production).")
154 (
"secondary-input", prog::value<vector<string>>(),
155 "Override name of input file for the secondary RootInput module used for the event embedding. Can be specified multiple times to use more than one file. Wildcards (as in *.root or [1-3].root) can be used, but need to be escaped with \\ or by quoting the argument to avoid expansion by the shell.")
157 (
"profile", prog::value<string>(),
158 "Name of a module to profile using callgrind. If more than one module of that name is registered only the first one will be profiled.")
162 prog::options_description cmdlineOptions;
163 cmdlineOptions.add(generic).add(config).add(advanced);
165 prog::positional_options_description posOptDesc;
166 posOptDesc.add(
"steering", 1);
167 posOptDesc.add(
"arg", -1);
169 prog::variables_map varMap;
170 prog::store(prog::command_line_parser(argc, argv).
171 options(cmdlineOptions).positional(posOptDesc).run(), varMap);
172 prog::notify(varMap);
175 if (varMap.count(
"help")) {
176 cout <<
"Usage: " << argv[0] <<
" [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
177 cout << cmdlineOptions << endl;
179 }
else if (varMap.count(
"version")) {
180 pythonFile =
"basf2/version.py";
181 }
else if (varMap.count(
"version-short")) {
182 pythonFile =
"basf2/version_short.py";
183 }
else if (varMap.count(
"info")) {
184 pythonFile =
"basf2_cli/print_info.py";
185 }
else if (varMap.count(
"license")) {
186 pythonFile =
"basf2_cli/print_license.py";
187 }
else if (varMap.count(
"modules")) {
188 string modArgs = varMap[
"modules"].as<
string>();
189 if (!modArgs.empty()) {
190 arguments.insert(arguments.begin(), modArgs);
195 if (varMap.count(
"steering")) {
196 arguments.insert(arguments.begin(), varMap[
"steering"].as<
string>());
198 pythonFile =
"basf2_cli/modules.py";
199 }
else if (varMap.count(
"module-io")) {
200 runModuleIOVisualization = varMap[
"module-io"].as<
string>();
201 pythonFile =
"basf2/core.py";
202 }
else if (varMap.count(
"execute-path")) {
204 pythonFile =
"basf2_cli/execute_pickled_path.py";
205 }
else if (varMap.count(
"steering")) {
207 pythonFile = varMap[
"steering"].as<
string>();
210 pythonFile =
"interactive.py";
213 if (!pythonFile.empty()) {
215 if (!std::filesystem::exists(pythonFile)) {
217 if (!libFile.empty())
218 pythonFile = libFile;
220 if (varMap.count(
"steering") and not varMap.count(
"modules")) {
221 B2INFO(
"Steering file: " << pythonFile);
227 if (varMap.count(
"processes")) {
228 int nprocesses = varMap[
"processes"].as<
int>();
229 if (nprocesses < 0) {
230 B2FATAL(
"Invalid number of processes!");
236 if (varMap.count(
"zmq")) {
242 if (varMap.count(
"profile")) {
243 string profileModule = varMap[
"profile"].as<
string>();
245 if (!RUNNING_ON_VALGRIND) {
247 cout <<
"Profiling requested, restarting using callgrind" << endl;
253 const vector<string> valgrind_argv {
254 "valgrind",
"--tool=callgrind",
"--instr-atstart=no",
"--trace-children=no",
255 "--callgrind-out-file=callgrind." + profileModule +
".%p",
258 cmd.reserve(valgrind_argv.size());
259 for (
const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
261 for (
int i = 0; i < argc; ++i) { cmd.push_back(argv[i]); }
263 cmd.push_back(
nullptr);
267 if (execvp(cmd[0], cmd.data()) == -1) {
269 perror(
"Problem calling valgrind");
282 if (varMap.count(
"events")) {
283 unsigned int nevents = varMap[
"events"].as<
unsigned int>();
284 if (nevents == 0 or nevents == std::numeric_limits<unsigned int>::max()) {
285 B2FATAL(
"Invalid number of events (valid range: 1.." << std::numeric_limits<unsigned int>::max() - 1 <<
")!");
290 if (varMap.count(
"experiment") or varMap.count(
"run")) {
291 if (!varMap.count(
"events"))
292 B2FATAL(
"--experiment and --run must be used with --events/-n!");
293 if (!(varMap.count(
"run") and varMap.count(
"experiment")))
294 B2FATAL(
"Both --experiment and --run must be specified!");
296 int run = varMap[
"run"].as<
int>();
297 int experiment = varMap[
"experiment"].as<
int>();
298 B2ASSERT(
"run must be >= 0!", run >= 0);
299 B2ASSERT(
"experiment must be >= 0!", experiment >= 0);
304 if (varMap.count(
"skip-events")) {
305 unsigned int skipevents = varMap[
"skip-events"].as<
unsigned int>();
310 if (varMap.count(
"input")) {
311 const auto& names = varMap[
"input"].as<vector<string>>();
316 if (varMap.count(
"sequence")) {
317 const auto& sequences = varMap[
"sequence"].as<vector<string>>();
322 if (varMap.count(
"output")) {
323 std::string name = varMap[
"output"].as<
string>();
328 if (varMap.count(
"log_level")) {
329 std::string levelParam = varMap[
"log_level"].as<
string>();
333 if (boost::iequals(levelParam, thisLevel)) {
339 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
349 if (varMap.count(
"package_log_level")) {
350 const auto& packLogList = varMap[
"package_log_level"].as<vector<string>>();
351 const std::string delimiter =
":";
352 for (
const std::string& packLog : packLogList) {
353 if (packLog.find(delimiter) == std::string::npos) {
354 B2FATAL(
"In --package_log_level input " << packLog <<
", no colon detected. ");
358 auto packageName = packLog.substr(0, packLog.find(delimiter));
359 std::string logName = packLog.substr(packLog.find(delimiter) + delimiter.length(), packLog.length());
361 if ((logName.find(
"DEBUG") != std::string::npos) && logName.length() > 5) {
363 debugLevel = std::stoi(logName.substr(logName.find(delimiter) + delimiter.length(), logName.length()));
364 }
catch (std::exception& e) {
365 B2WARNING(
"In --package_log_level, issue parsing debugLevel. Still setting log level to DEBUG.");
374 if (boost::iequals(logName, thisLevel)) {
380 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
383 if ((logName ==
"DEBUG") && (debugLevel >= 0)) {
392 if (varMap.count(
"debug_level")) {
393 unsigned int level = varMap[
"debug_level"].as<
unsigned int>();
398 if (varMap.count(
"visualize-dataflow")) {
401 B2WARNING(
"--visualize-dataflow cannot be used with parallel processing, no graphs will be saved!");
405 if (varMap.count(
"no-stats")) {
409 if (varMap.count(
"dry-run")) {
413 if (varMap.count(
"dump-path")) {
417 if (varMap.count(
"random-seed")) {
421 if (varMap.count(
"job-information")) {
422 string jobInfoFile = varMap[
"job-information"].as<
string>();
424 B2INFO(
"Job information file: " << jobInfoFile);
427 if (varMap.count(
"realm")) {
428 std::string realmParam = varMap[
"realm"].as<
string>();
432 if (boost::iequals(realmParam, thisRealm)) {
438 B2FATAL(
"Invalid realm! Needs to be one of online or production.");
443 if (varMap.count(
"secondary-input")) {
444 const auto& names = varMap[
"secondary-input"].as<vector<string>>();
448 }
catch (exception& e) {
449 cerr <<
"error: " << e.what() << endl;
452 cerr <<
"Exception of unknown type!" << endl;
461 PyConfig_InitPythonConfig(&config);
462 config.install_signal_handlers = 0;
463 config.safe_path = 0;
465 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
467 std::vector<wstring> pyArgvString(arguments.size() + 2);
470 pyArgvString[0] = L
"python3";
471 pyArgvString[1] = converter.from_bytes(pythonFile);
472 for (
size_t i = 0; i < arguments.size(); i++) {
473 pyArgvString[i + 2] = converter.from_bytes(arguments[i]);
475 std::vector<const wchar_t*> pyArgvArray(pyArgvString.size());
476 for (
size_t i = 0; i < pyArgvString.size(); ++i) {
477 pyArgvArray[i] = pyArgvString[i].c_str();
482 status = PyConfig_SetArgv(&config, pyArgvArray.size(),
const_cast<wchar_t**
>(pyArgvArray.data()));
483 checkPythonStatus(config, status);
485 status = Py_InitializeFromConfig(&config);
486 checkPythonStatus(config, status);
488 auto fullPath = std::filesystem::absolute(std::filesystem::path(pythonFile));
490 if ((std::filesystem::is_directory(fullPath)) || !(std::filesystem::exists(fullPath))) {
491 B2FATAL(
"The given filename and/or path is not valid: " + pythonFile);
494 std::ifstream file(fullPath.string().c_str());
495 std::stringstream buffer;
496 buffer << file.rdbuf();
498 int pyReturnValue = Py_RunMain();
501 PyConfig_Clear(&config);
505 if (!runModuleIOVisualization.empty()) {
518 return pyReturnValue;
static void executeModuleAndCreateIOPlot(const std::string &module)
Create independent I/O graph for a single module (without requiring a steering file).
void setOutputFileOverride(const std::string &name)
Override output file name for modules.
void setProfileModuleName(const std::string &name)
Set the name of a module to be profiled.
void setSteering(const std::string &steering)
Sets the steering file content.
void setNumberEventsOverride(unsigned int nevents)
Override the number of events in run 1 for EventInfoSetter module.
void setRealm(LogConfig::ELogRealm realm)
Set the basf2 execution realm.
void setRunExperimentOverride(int run, int experiment)
Override run and experiment for EventInfoSetter.
void setNumberProcessesOverride(int nproc)
Override number of processes to run in parallel.
void setLogLevelOverride(int level)
Override global log level if != LogConfig::c_Default.
void printJobInformation() const
Print information on input/output files in current steering file, used by –dry-run.
void setEntrySequencesOverride(const std::vector< std::string > &sequences)
Override the number sequences (e.g.
static Environment & Instance()
Static method to get a reference to the Environment instance.
void setSecondaryInputFilesOverride(const std::vector< std::string > &names)
Override secondary input file names for modules.
void setUseZMQ(bool useZMQ)
Set the flag if ZMQ should be used instead of the RingBuffer multiprocessing implementation.
void setPicklePath(const std::string &path)
Sets the path to the file where the pickled path is stored.
void setSkipEventsOverride(unsigned int skipEvents)
Set skipNEvents override.
void setVisualizeDataFlow(bool on)
Wether to generate DOT files with data store inputs/outputs of each module.
void setInputFilesOverride(const std::vector< std::string > &names)
Override input file names for modules.
void setNoStats(bool noStats)
Disable collection of statistics during event processing.
void setDryRun(bool dryRun)
Read steering file, but do not start any actually start any event processing.
static std::string findFile(const std::string &path, bool silent=false)
Search for given file or directory in local or central release directory, and return absolute path if...
void setDebugLevel(int debugLevel)
Configure the debug messaging level.
static const char * logRealmToString(ELogRealm realm)
Converts a log realm type to a string.
ELogLevel
Definition of the supported log levels.
@ c_Debug
Debug: for code development.
@ c_Fatal
Fatal: for situations were the program execution can not be continued.
ELogRealm
Definition of the supported execution realms.
@ c_Online
Online data taking.
@ c_Production
Data production jobs.
void setLogLevel(ELogLevel logLevel)
Configure the log level.
static const char * logLevelToString(ELogLevel logLevelType)
Converts a log level type to a string.
LogConfig * getLogConfig()
Returns global log system configuration.
static LogSystem & Instance()
Static method to get a reference to the LogSystem instance.
LogConfig & getPackageLogConfig(const std::string &package)
Get the log configuration for the package with the given name.
static void initialize()
Initialize the random number generator with a unique random seed;.
Abstract base class for different kinds of events.