18 #include <boost/python.hpp>
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>
52 using namespace boost::python;
54 namespace prog = boost::program_options;
57 void executePythonFile(
const string& pythonFile)
61 import(
"ROOT").attr(
"PyConfig").attr(
"DisableRootLogon") =
true;
63 object main_module =
import(
"__main__");
64 object main_namespace = main_module.attr(
"__dict__");
65 if (pythonFile.empty()) {
67 object interactive =
import(
"interactive");
68 main_namespace[
"__b2shell_config"] = interactive.attr(
"basf2_shell_config")();
69 exec(
"import IPython; "
70 " from basf2 import *; "
71 "IPython.embed(config=__b2shell_config, header=f\"Welcome to {basf2label}\"); ",
72 main_namespace, main_namespace);
76 auto fullPath = std::filesystem::absolute(std::filesystem::path(pythonFile));
77 if ((!(std::filesystem::is_directory(fullPath))) && (std::filesystem::exists(fullPath))) {
79 std::ifstream file(fullPath.string().c_str());
80 std::stringstream buffer;
81 buffer << file.rdbuf();
82 Environment::Instance().setSteering(buffer.str());
83 exec_file(boost::python::str(fullPath.string()), main_namespace, main_namespace);
85 B2FATAL(
"The given filename and/or path is not valid: " + pythonFile);
90 int main(
int argc,
char* argv[])
95 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
96 B2FATAL(
"Cannot remove SIGPIPE signal handler");
100 MetadataService::Instance();
103 Environment::Instance();
106 const char* belle2SubDir = getenv(
"BELLE2_SUBDIR");
107 std::filesystem::path libPath =
"lib";
108 libPath /= belle2SubDir;
110 string runModuleIOVisualization(
"");
111 vector<string> arguments;
119 prog::options_description
generic(
"Generic options (to be used instead of steering file)");
120 generic.add_options()
121 (
"help,h",
"Print this help")
122 (
"version,v",
"Print long and verbose version string")
123 (
"version-short",
"Print short version string")
124 (
"info",
"Print information about basf2")
125 (
"license",
"Print the short version of the basf2 license")
126 (
"modules,m", prog::value<string>()->implicit_value(
""),
127 "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).")
130 prog::options_description config(
"Configuration");
132 (
"steering", prog::value<string>(),
"The python steering file to run.")
133 (
"arg", prog::value<vector<string> >(&arguments),
"Additional arguments to be passed to the steering file")
134 (
"log_level,l", prog::value<string>(),
135 "Set global log level (one of DEBUG, INFO, RESULT, WARNING, or ERROR). Takes precedence over set_log_level() in steering file.")
136 (
"package_log_level", prog::value<vector<string> >(),
137 "Set package log level. Can be specified multiple times to use more than one package. (Examples: 'klm:INFO or cdc:DEBUG:10') ")
138 (
"random-seed", prog::value<string>(),
139 "Set the default initial seed for the random number generator. "
140 "This does not take precedence over calls to set_random_seed() in the steering file, but just changes the default. "
141 "If no seed is set via either of these mechanisms, the initial seed will be taken from the system's entropy pool.")
142 (
"debug_level,d", prog::value<unsigned int>(),
"Set default debug level. Also sets the log level to DEBUG.")
143 (
"events,n", prog::value<unsigned int>(),
"Override number of events for EventInfoSetter; otherwise set maximum number of events.")
144 (
"run", prog::value<int>(),
"Override run for EventInfoSetter, must be used with -n and --experiment")
145 (
"experiment", prog::value<int>(),
"Override experiment for EventInfoSetter, must be used with -n and --run")
146 (
"skip-events", prog::value<unsigned int>(),
147 "Override skipNEvents for EventInfoSetter and RootInput. Skips this many events before starting.")
148 (
"input,i", prog::value<vector<string> >(),
149 "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.")
150 (
"sequence,S", prog::value<vector<string> >(),
151 "Override the number sequence (e.g. 23:42,101) defining the entries (starting from 0) which are processed by RootInput."
152 "Must be specified exactly once for each file to be opened."
153 "This means one sequence per input file AFTER wildcard expansion."
154 "The first event has the number 0.")
155 (
"output,o", prog::value<string>(),
156 "Override name of output file for (Seq)RootOutput. In case multiple modules are present in the path, only the first will be affected.")
157 (
"processes,p", prog::value<int>(),
"Override number of worker processes (>=1 enables, 0 disables parallel processing)");
159 prog::options_description advanced(
"Advanced Options");
160 advanced.add_options()
161 (
"module-io", prog::value<string>(),
162 "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'.")
163 (
"visualize-dataflow",
"Generate data flow diagram (dataflow.dot) for the executed steering file.")
165 "Disable collection of statistics during event processing. Useful for very high-rate applications, but produces empty table with 'print(statistics)'.")
167 "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.")
168 (
"dump-path", prog::value<string>(),
169 "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.")
170 (
"execute-path", prog::value<string>(),
171 "Do not read any provided steering file, instead execute the pickled (serialized) path from the given file.")
173 "Use ZMQ for multiprocessing instead of a RingBuffer. This has many implications and should only be used by experts.")
174 (
"job-information", prog::value<string>(),
175 "Create json file with metadata of output files and basf2 execution status.")
176 (
"realm", prog::value<string>(),
177 "Set the realm of the basf2 execution (online or production).")
178 (
"secondary-input", prog::value<vector<string>>(),
179 "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.")
181 (
"profile", prog::value<string>(),
182 "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.")
186 prog::options_description cmdlineOptions;
187 cmdlineOptions.add(
generic).add(config).add(advanced);
189 prog::positional_options_description posOptDesc;
190 posOptDesc.add(
"steering", 1);
191 posOptDesc.add(
"arg", -1);
193 prog::variables_map varMap;
194 prog::store(prog::command_line_parser(argc, argv).
195 options(cmdlineOptions).positional(posOptDesc).run(), varMap);
196 prog::notify(varMap);
199 if (varMap.count(
"help")) {
200 cout <<
"Usage: " << argv[0] <<
" [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
201 cout << cmdlineOptions << endl;
203 }
else if (varMap.count(
"version")) {
204 pythonFile =
"basf2/version.py";
205 }
else if (varMap.count(
"version-short")) {
206 pythonFile =
"basf2/version_short.py";
207 }
else if (varMap.count(
"info")) {
208 pythonFile =
"basf2_cli/print_info.py";
209 }
else if (varMap.count(
"license")) {
210 pythonFile =
"basf2_cli/print_license.py";
211 }
else if (varMap.count(
"modules")) {
212 string modArgs = varMap[
"modules"].as<
string>();
213 if (!modArgs.empty()) {
214 arguments.insert(arguments.begin(), modArgs);
219 if (varMap.count(
"steering")) {
220 arguments.insert(arguments.begin(), varMap[
"steering"].as<
string>());
222 pythonFile =
"basf2_cli/modules.py";
223 }
else if (varMap.count(
"module-io")) {
224 runModuleIOVisualization = varMap[
"module-io"].as<
string>();
225 pythonFile =
"basf2/core.py";
226 }
else if (varMap.count(
"execute-path")) {
227 Environment::Instance().setPicklePath(varMap[
"execute-path"].as<string>());
228 pythonFile =
"basf2_cli/execute_pickled_path.py";
229 }
else if (varMap.count(
"steering")) {
231 pythonFile = varMap[
"steering"].as<
string>();
234 if (!pythonFile.empty()) {
236 if (!std::filesystem::exists(pythonFile)) {
237 std::string libFile = FileSystem::findFile((libPath / pythonFile).
string(),
true);
238 if (!libFile.empty())
239 pythonFile = libFile;
241 if (varMap.count(
"steering") and not varMap.count(
"modules")) {
242 B2INFO(
"Steering file: " << pythonFile);
248 if (varMap.count(
"processes")) {
249 int nprocesses = varMap[
"processes"].as<
int>();
250 if (nprocesses < 0) {
251 B2FATAL(
"Invalid number of processes!");
253 Environment::Instance().setNumberProcessesOverride(nprocesses);
257 if (varMap.count(
"zmq")) {
258 Environment::Instance().setUseZMQ(
true);
263 if (varMap.count(
"profile")) {
264 string profileModule = varMap[
"profile"].as<
string>();
266 if (!RUNNING_ON_VALGRIND) {
268 cout <<
"Profiling requested, restarting using callgrind" << endl;
274 const vector<string> valgrind_argv {
275 "valgrind",
"--tool=callgrind",
"--instr-atstart=no",
"--trace-children=no",
276 "--callgrind-out-file=callgrind." + profileModule +
".%p",
279 cmd.reserve(valgrind_argv.size());
280 for (
const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
282 for (
int i = 0; i < argc; ++i) { cmd.push_back(argv[i]); }
284 cmd.push_back(
nullptr);
288 if (execvp(cmd[0], cmd.data()) == -1) {
290 perror(
"Problem calling valgrind");
296 Environment::Instance().setProfileModuleName(profileModule);
298 Environment::Instance().setNumberProcessesOverride(0);
303 if (varMap.count(
"events")) {
304 unsigned int nevents = varMap[
"events"].as<
unsigned int>();
305 if (nevents == 0 or nevents == std::numeric_limits<unsigned int>::max()) {
306 B2FATAL(
"Invalid number of events (valid range: 1.." << std::numeric_limits<unsigned int>::max() - 1 <<
")!");
308 Environment::Instance().setNumberEventsOverride(nevents);
311 if (varMap.count(
"experiment") or varMap.count(
"run")) {
312 if (!varMap.count(
"events"))
313 B2FATAL(
"--experiment and --run must be used with --events/-n!");
314 if (!(varMap.count(
"run") and varMap.count(
"experiment")))
315 B2FATAL(
"Both --experiment and --run must be specified!");
317 int run = varMap[
"run"].as<
int>();
318 int experiment = varMap[
"experiment"].as<
int>();
319 B2ASSERT(
"run must be >= 0!", run >= 0);
320 B2ASSERT(
"experiment must be >= 0!", experiment >= 0);
321 Environment::Instance().setRunExperimentOverride(run, experiment);
325 if (varMap.count(
"skip-events")) {
326 unsigned int skipevents = varMap[
"skip-events"].as<
unsigned int>();
327 Environment::Instance().setSkipEventsOverride(skipevents);
331 if (varMap.count(
"input")) {
332 const auto& names = varMap[
"input"].as<vector<string>>();
333 Environment::Instance().setInputFilesOverride(names);
337 if (varMap.count(
"sequence")) {
338 const auto& sequences = varMap[
"sequence"].as<vector<string>>();
339 Environment::Instance().setEntrySequencesOverride(sequences);
343 if (varMap.count(
"output")) {
344 std::string name = varMap[
"output"].as<
string>();
345 Environment::Instance().setOutputFileOverride(name);
349 if (varMap.count(
"log_level")) {
350 std::string levelParam = varMap[
"log_level"].as<
string>();
352 for (
int i = LogConfig::c_Debug; i < LogConfig::c_Fatal; i++) {
354 if (boost::iequals(levelParam, thisLevel)) {
360 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
366 Environment::Instance().setLogLevelOverride(level);
370 if (varMap.count(
"package_log_level")) {
371 const auto& packLogList = varMap[
"package_log_level"].as<vector<string>>();
372 const std::string delimiter =
":";
373 for (
const std::string& packLog : packLogList) {
374 if (packLog.find(delimiter) == std::string::npos) {
375 B2FATAL(
"In --package_log_level input " << packLog <<
", no colon detected. ");
379 auto packageName = packLog.substr(0, packLog.find(delimiter));
380 std::string logName = packLog.substr(packLog.find(delimiter) + delimiter.length(), packLog.length());
382 if ((logName.find(
"DEBUG") != std::string::npos) && logName.length() > 5) {
384 debugLevel = std::stoi(logName.substr(logName.find(delimiter) + delimiter.length(), logName.length()));
385 }
catch (std::exception& e) {
386 B2WARNING(
"In --package_log_level, issue parsing debugLevel. Still setting log level to DEBUG.");
393 for (
int i = LogConfig::c_Debug; i < LogConfig::c_Fatal; i++) {
395 if (boost::iequals(logName, thisLevel)) {
401 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
404 if ((logName ==
"DEBUG") && (debugLevel >= 0)) {
405 LogSystem::Instance().getPackageLogConfig(packageName).setDebugLevel(debugLevel);
407 LogSystem::Instance().getPackageLogConfig(packageName).setLogLevel((
LogConfig::ELogLevel)level);
413 if (varMap.count(
"debug_level")) {
414 unsigned int level = varMap[
"debug_level"].as<
unsigned int>();
415 LogSystem::Instance().getLogConfig()->setDebugLevel(level);
416 LogSystem::Instance().getLogConfig()->setLogLevel(LogConfig::c_Debug);
419 if (varMap.count(
"visualize-dataflow")) {
420 Environment::Instance().setVisualizeDataFlow(
true);
421 if (Environment::Instance().getNumberProcesses() > 0) {
422 B2WARNING(
"--visualize-dataflow cannot be used with parallel processing, no graphs will be saved!");
426 if (varMap.count(
"no-stats")) {
427 Environment::Instance().setNoStats(
true);
430 if (varMap.count(
"dry-run")) {
431 Environment::Instance().setDryRun(
true);
434 if (varMap.count(
"dump-path")) {
435 Environment::Instance().setPicklePath(varMap[
"dump-path"].as<string>());
438 if (varMap.count(
"random-seed")) {
439 RandomNumbers::initialize(varMap[
"random-seed"].as<string>());
442 if (varMap.count(
"job-information")) {
443 string jobInfoFile = varMap[
"job-information"].as<
string>();
444 MetadataService::Instance().setJsonFileName(jobInfoFile);
445 B2INFO(
"Job information file: " << jobInfoFile);
448 if (varMap.count(
"realm")) {
449 std::string realmParam = varMap[
"realm"].as<
string>();
451 for (
int i = LogConfig::c_Online; i <= LogConfig::c_Production; i++) {
453 if (boost::iequals(realmParam, thisRealm)) {
459 B2FATAL(
"Invalid realm! Needs to be one of online or production.");
464 if (varMap.count(
"secondary-input")) {
465 const auto& names = varMap[
"secondary-input"].as<vector<string>>();
466 Environment::Instance().setSecondaryInputFilesOverride(names);
469 }
catch (exception& e) {
470 cerr <<
"error: " << e.what() << endl;
473 cerr <<
"Exception of unknown type!" << endl;
484 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
485 std::vector<wstring> pyArgvString(arguments.size() + 1);
487 if (!pythonFile.empty()) {
488 pyArgvString[0] = converter.from_bytes(pythonFile);
490 pyArgvString[0] = converter.from_bytes(argv[0]);
492 for (
size_t i = 0; i < arguments.size(); i++) {
493 pyArgvString[i + 1] = converter.from_bytes(arguments[i]);
495 std::vector<const wchar_t*> pyArgvArray(pyArgvString.size());
496 for (
size_t i = 0; i < pyArgvString.size(); ++i) {
497 pyArgvArray[i] = pyArgvString[i].c_str();
500 PySys_SetArgv(pyArgvArray.size(),
const_cast<wchar_t**
>(pyArgvArray.data()));
503 executePythonFile(pythonFile);
509 if (!runModuleIOVisualization.empty()) {
510 DataFlowVisualization::executeModuleAndCreateIOPlot(runModuleIOVisualization);
514 if (Environment::Instance().getDryRun()) {
515 Environment::Instance().printJobInformation();
519 MetadataService::Instance().addBasf2Status(
"finished successfully");
520 MetadataService::Instance().finishBasf2();
521 }
catch (error_already_set&) {
ELogLevel
Definition of the supported log levels.
ELogRealm
Definition of the supported execution realms.
Abstract base class for different kinds of events.
int main(int argc, char **argv)
Run all tests.