18 #include <boost/python.hpp>
20 #include <framework/core/Environment.h>
21 #include <framework/core/DataFlowVisualization.h>
22 #include <framework/core/RandomNumbers.h>
23 #include <framework/logging/Logger.h>
24 #include <framework/logging/LogConfig.h>
25 #include <framework/logging/LogSystem.h>
26 #include <framework/utilities/FileSystem.h>
27 #include <framework/core/MetadataService.h>
29 #include <boost/program_options.hpp>
30 #include <boost/algorithm/string/predicate.hpp>
44 #include <valgrind/valgrind.h>
49 using namespace boost::python;
51 namespace prog = boost::program_options;
54 void executePythonFile(
const string& pythonFile)
58 import(
"ROOT").attr(
"PyConfig").attr(
"DisableRootLogon") =
true;
60 object main_module =
import(
"__main__");
61 object main_namespace = main_module.attr(
"__dict__");
62 if (pythonFile.empty()) {
64 object interactive =
import(
"interactive");
65 main_namespace[
"__b2shell_config"] = interactive.attr(
"basf2_shell_config")();
66 exec(
"import IPython; "
67 " from basf2 import *; "
68 "IPython.embed(config=__b2shell_config, header=f\"Welcome to {basf2label}\"); ",
69 main_namespace, main_namespace);
73 auto fullPath = std::filesystem::absolute(std::filesystem::path(pythonFile));
74 if ((!(std::filesystem::is_directory(fullPath))) && (std::filesystem::exists(fullPath))) {
76 std::ifstream file(fullPath.string().c_str());
77 std::stringstream buffer;
78 buffer << file.rdbuf();
79 Environment::Instance().setSteering(buffer.str());
80 exec_file(boost::python::str(fullPath.string()), main_namespace, main_namespace);
82 B2FATAL(
"The given filename and/or path is not valid: " + pythonFile);
87 int main(
int argc,
char* argv[])
92 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
93 B2FATAL(
"Cannot remove SIGPIPE signal handler");
97 MetadataService::Instance();
100 Environment::Instance();
103 const char* belle2SubDir = getenv(
"BELLE2_SUBDIR");
104 std::filesystem::path libPath =
"lib";
105 libPath /= belle2SubDir;
107 string runModuleIOVisualization(
"");
108 vector<string> arguments;
116 prog::options_description
generic(
"Generic options (to be used instead of steering file)");
117 generic.add_options()
118 (
"help,h",
"Print this help")
119 (
"version,v",
"Print long and verbose version string")
120 (
"version-short",
"Print short version string")
121 (
"info",
"Print information about basf2")
122 (
"license",
"Print the short version of the basf2 license")
123 (
"modules,m", prog::value<string>()->implicit_value(
""),
124 "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).")
127 prog::options_description config(
"Configuration");
129 (
"steering", prog::value<string>(),
"The python steering file to run.")
130 (
"arg", prog::value<vector<string> >(&arguments),
"Additional arguments to be passed to the steering file")
131 (
"log_level,l", prog::value<string>(),
132 "Set global log level (one of DEBUG, INFO, RESULT, WARNING, or ERROR). Takes precedence over set_log_level() in steering file.")
133 (
"random-seed", prog::value<string>(),
134 "Set the default initial seed for the random number generator. "
135 "This does not take precedence over calls to set_random_seed() in the steering file, but just changes the default. "
136 "If no seed is set via either of these mechanisms, the initial seed will be taken from the system's entropy pool.")
137 (
"debug_level,d", prog::value<unsigned int>(),
"Set default debug level. Also sets the log level to DEBUG.")
138 (
"events,n", prog::value<unsigned int>(),
"Override number of events for EventInfoSetter; otherwise set maximum number of events.")
139 (
"run", prog::value<int>(),
"Override run for EventInfoSetter, must be used with -n and --experiment")
140 (
"experiment", prog::value<int>(),
"Override experiment for EventInfoSetter, must be used with -n and --run")
141 (
"skip-events", prog::value<unsigned int>(),
142 "Override skipNEvents for EventInfoSetter and RootInput. Skips this many events before starting.")
143 (
"input,i", prog::value<vector<string> >(),
144 "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.")
145 (
"sequence,S", prog::value<vector<string> >(),
146 "Override the number sequence (e.g. 23:42,101) defining the entries (starting from 0) which are processed by RootInput."
147 "Must be specified exactly once for each file to be opened."
148 "This means one sequence per input file AFTER wildcard expansion."
149 "The first event has the number 0.")
150 (
"output,o", prog::value<string>(),
151 "Override name of output file for (Seq)RootOutput. In case multiple modules are present in the path, only the first will be affected.")
152 (
"processes,p", prog::value<int>(),
"Override number of worker processes (>=1 enables, 0 disables parallel processing)");
154 prog::options_description advanced(
"Advanced Options");
155 advanced.add_options()
156 (
"module-io", prog::value<string>(),
157 "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'.")
158 (
"visualize-dataflow",
"Generate data flow diagram (dataflow.dot) for the executed steering file.")
160 "Disable collection of statistics during event processing. Useful for very high-rate applications, but produces empty table with 'print(statistics)'.")
162 "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.")
163 (
"dump-path", prog::value<string>(),
164 "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.")
165 (
"execute-path", prog::value<string>(),
166 "Do not read any provided steering file, instead execute the pickled (serialized) path from the given file.")
168 "Use ZMQ for multiprocessing instead of a RingBuffer. This has many implications and should only be used by experts.")
169 (
"job-information", prog::value<string>(),
170 "Create json file with metadata of output files and basf2 execution status.")
171 (
"realm", prog::value<string>(),
172 "Set the realm of the basf2 execution (online or production).")
173 (
"secondary-input", prog::value<vector<string>>(),
174 "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.")
176 (
"profile", prog::value<string>(),
177 "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.")
181 prog::options_description cmdlineOptions;
182 cmdlineOptions.add(
generic).add(config).add(advanced);
184 prog::positional_options_description posOptDesc;
185 posOptDesc.add(
"steering", 1);
186 posOptDesc.add(
"arg", -1);
188 prog::variables_map varMap;
189 prog::store(prog::command_line_parser(argc, argv).
190 options(cmdlineOptions).positional(posOptDesc).run(), varMap);
191 prog::notify(varMap);
194 if (varMap.count(
"help")) {
195 cout <<
"Usage: " << argv[0] <<
" [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
196 cout << cmdlineOptions << endl;
198 }
else if (varMap.count(
"version")) {
199 pythonFile =
"basf2/version.py";
200 }
else if (varMap.count(
"version-short")) {
201 pythonFile =
"basf2/version_short.py";
202 }
else if (varMap.count(
"info")) {
203 pythonFile =
"basf2_cli/print_info.py";
204 }
else if (varMap.count(
"license")) {
205 pythonFile =
"basf2_cli/print_license.py";
206 }
else if (varMap.count(
"modules")) {
207 string modArgs = varMap[
"modules"].as<
string>();
208 if (!modArgs.empty()) {
209 arguments.insert(arguments.begin(), modArgs);
214 if (varMap.count(
"steering")) {
215 arguments.insert(arguments.begin(), varMap[
"steering"].as<
string>());
217 pythonFile =
"basf2_cli/modules.py";
218 }
else if (varMap.count(
"module-io")) {
219 runModuleIOVisualization = varMap[
"module-io"].as<
string>();
220 pythonFile =
"basf2/core.py";
221 }
else if (varMap.count(
"execute-path")) {
222 Environment::Instance().setPicklePath(varMap[
"execute-path"].as<string>());
223 pythonFile =
"basf2_cli/execute_pickled_path.py";
224 }
else if (varMap.count(
"steering")) {
226 pythonFile = varMap[
"steering"].as<
string>();
227 B2INFO(
"Steering file: " << pythonFile);
233 if (varMap.count(
"processes")) {
234 int nprocesses = varMap[
"processes"].as<
int>();
235 if (nprocesses < 0) {
236 B2FATAL(
"Invalid number of processes!");
238 Environment::Instance().setNumberProcessesOverride(nprocesses);
242 if (varMap.count(
"zmq")) {
243 Environment::Instance().setUseZMQ(
true);
248 if (varMap.count(
"profile")) {
249 string profileModule = varMap[
"profile"].as<
string>();
251 if (!RUNNING_ON_VALGRIND) {
253 cout <<
"Profiling requested, restarting using callgrind" << endl;
259 const vector<string> valgrind_argv {
260 "valgrind",
"--tool=callgrind",
"--instr-atstart=no",
"--trace-children=no",
261 "--callgrind-out-file=callgrind." + profileModule +
".%p",
264 cmd.reserve(valgrind_argv.size());
265 for (
const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
267 for (
int i = 0; i < argc; ++i) { cmd.push_back(argv[i]); }
269 cmd.push_back(
nullptr);
273 if (execvp(cmd[0], cmd.data()) == -1) {
275 perror(
"Problem calling valgrind");
281 Environment::Instance().setProfileModuleName(profileModule);
283 Environment::Instance().setNumberProcessesOverride(0);
288 if (varMap.count(
"events")) {
289 unsigned int nevents = varMap[
"events"].as<
unsigned int>();
290 if (nevents == 0 or nevents == std::numeric_limits<unsigned int>::max()) {
291 B2FATAL(
"Invalid number of events (valid range: 1.." << std::numeric_limits<unsigned int>::max() - 1 <<
")!");
293 Environment::Instance().setNumberEventsOverride(nevents);
296 if (varMap.count(
"experiment") or varMap.count(
"run")) {
297 if (!varMap.count(
"events"))
298 B2FATAL(
"--experiment and --run must be used with --events/-n!");
299 if (!(varMap.count(
"run") and varMap.count(
"experiment")))
300 B2FATAL(
"Both --experiment and --run must be specified!");
302 int run = varMap[
"run"].as<
int>();
303 int experiment = varMap[
"experiment"].as<
int>();
304 B2ASSERT(
"run must be >= 0!", run >= 0);
305 B2ASSERT(
"experiment must be >= 0!", experiment >= 0);
306 Environment::Instance().setRunExperimentOverride(run, experiment);
310 if (varMap.count(
"skip-events")) {
311 unsigned int skipevents = varMap[
"skip-events"].as<
unsigned int>();
312 Environment::Instance().setSkipEventsOverride(skipevents);
316 if (varMap.count(
"input")) {
317 const auto& names = varMap[
"input"].as<vector<string>>();
318 Environment::Instance().setInputFilesOverride(names);
322 if (varMap.count(
"sequence")) {
323 const auto& sequences = varMap[
"sequence"].as<vector<string>>();
324 Environment::Instance().setEntrySequencesOverride(sequences);
328 if (varMap.count(
"output")) {
329 std::string name = varMap[
"output"].as<
string>();
330 Environment::Instance().setOutputFileOverride(name);
334 if (varMap.count(
"log_level")) {
335 std::string levelParam = varMap[
"log_level"].as<
string>();
337 for (
int i = LogConfig::c_Debug; i < LogConfig::c_Fatal; i++) {
339 if (boost::iequals(levelParam, thisLevel)) {
345 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
351 Environment::Instance().setLogLevelOverride(level);
355 if (varMap.count(
"debug_level")) {
356 unsigned int level = varMap[
"debug_level"].as<
unsigned int>();
357 LogSystem::Instance().getLogConfig()->setDebugLevel(level);
358 LogSystem::Instance().getLogConfig()->setLogLevel(LogConfig::c_Debug);
361 if (varMap.count(
"visualize-dataflow")) {
362 Environment::Instance().setVisualizeDataFlow(
true);
363 if (Environment::Instance().getNumberProcesses() > 0) {
364 B2WARNING(
"--visualize-dataflow cannot be used with parallel processing, no graphs will be saved!");
368 if (varMap.count(
"no-stats")) {
369 Environment::Instance().setNoStats(
true);
372 if (varMap.count(
"dry-run")) {
373 Environment::Instance().setDryRun(
true);
376 if (varMap.count(
"dump-path")) {
377 Environment::Instance().setPicklePath(varMap[
"dump-path"].as<string>());
380 if (varMap.count(
"random-seed")) {
381 RandomNumbers::initialize(varMap[
"random-seed"].as<string>());
384 if (varMap.count(
"job-information")) {
385 string jobInfoFile = varMap[
"job-information"].as<
string>();
386 MetadataService::Instance().setJsonFileName(jobInfoFile);
387 B2INFO(
"Job information file: " << jobInfoFile);
390 if (varMap.count(
"realm")) {
391 std::string realmParam = varMap[
"realm"].as<
string>();
393 for (
int i = LogConfig::c_Online; i <= LogConfig::c_Production; i++) {
395 if (boost::iequals(realmParam, thisRealm)) {
401 B2FATAL(
"Invalid realm! Needs to be one of online or production.");
406 if (varMap.count(
"secondary-input")) {
407 const auto& names = varMap[
"secondary-input"].as<vector<string>>();
408 Environment::Instance().setSecondaryInputFilesOverride(names);
411 }
catch (exception& e) {
412 cerr <<
"error: " << e.what() << endl;
415 cerr <<
"Exception of unknown type!" << endl;
422 if (!pythonFile.empty()) {
424 if (!std::filesystem::exists(pythonFile)) {
425 std::string libFile = FileSystem::findFile((libPath / pythonFile).
string(),
true);
426 if (!libFile.empty())
427 pythonFile = libFile;
435 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
436 std::vector<wstring> pyArgvString(arguments.size() + 1);
438 if (!pythonFile.empty()) {
439 pyArgvString[0] = converter.from_bytes(pythonFile);
441 pyArgvString[0] = converter.from_bytes(argv[0]);
443 for (
size_t i = 0; i < arguments.size(); i++) {
444 pyArgvString[i + 1] = converter.from_bytes(arguments[i]);
446 std::vector<const wchar_t*> pyArgvArray(pyArgvString.size());
447 for (
size_t i = 0; i < pyArgvString.size(); ++i) {
448 pyArgvArray[i] = pyArgvString[i].c_str();
451 PySys_SetArgv(pyArgvArray.size(),
const_cast<wchar_t**
>(pyArgvArray.data()));
454 executePythonFile(pythonFile);
460 if (!runModuleIOVisualization.empty()) {
461 DataFlowVisualization::executeModuleAndCreateIOPlot(runModuleIOVisualization);
465 if (Environment::Instance().getDryRun()) {
466 Environment::Instance().printJobInformation();
470 MetadataService::Instance().addBasf2Status(
"finished successfully");
471 MetadataService::Instance().finishBasf2();
472 }
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.