33 #include <boost/python.hpp>
35 #include <framework/core/Environment.h>
36 #include <framework/core/DataFlowVisualization.h>
37 #include <framework/core/RandomNumbers.h>
38 #include <framework/logging/Logger.h>
39 #include <framework/logging/LogConfig.h>
40 #include <framework/logging/LogSystem.h>
41 #include <framework/utilities/FileSystem.h>
42 #include <framework/core/MetadataService.h>
44 #include <boost/program_options.hpp>
45 #include <boost/filesystem.hpp>
46 #include <boost/algorithm/string/predicate.hpp>
59 #include <valgrind/valgrind.h>
64 using namespace boost::python;
66 namespace prog = boost::program_options;
69 void executePythonFile(
const string& pythonFile)
73 import(
"ROOT").attr(
"PyConfig").attr(
"DisableRootLogon") =
true;
75 object main_module =
import(
"__main__");
76 object main_namespace = main_module.attr(
"__dict__");
77 if (pythonFile.empty()) {
79 object interactive =
import(
"interactive");
80 main_namespace[
"__b2shell_config"] = interactive.attr(
"basf2_shell_config")();
81 exec(
"import IPython; "
82 " from basf2 import *; "
83 "IPython.embed(config=__b2shell_config, header=f\"Welcome to {basf2label}\"); ",
84 main_namespace, main_namespace);
88 auto fullPath = boost::filesystem::system_complete(boost::filesystem::path(pythonFile));
89 if ((!(boost::filesystem::is_directory(fullPath))) && (boost::filesystem::exists(fullPath))) {
91 std::ifstream file(fullPath.string().c_str());
92 std::stringstream buffer;
93 buffer << file.rdbuf();
94 Environment::Instance().setSteering(buffer.str());
95 exec_file(boost::python::str(fullPath.string()), main_namespace, main_namespace);
97 B2FATAL(
"The given filename and/or path is not valid: " + pythonFile);
102 int main(
int argc,
char* argv[])
107 if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
108 B2FATAL(
"Cannot remove SIGPIPE signal handler");
112 MetadataService::Instance();
115 Environment::Instance();
118 const char* belle2SubDir = getenv(
"BELLE2_SUBDIR");
119 boost::filesystem::path libPath =
"lib";
120 libPath /= belle2SubDir;
122 string runModuleIOVisualization(
"");
123 vector<string> arguments;
131 prog::options_description
generic(
"Generic options (to be used instead of steering file)");
132 generic.add_options()
133 (
"help,h",
"Print this help")
134 (
"version,v",
"Print version string")
135 (
"info",
"Print information about basf2")
136 (
"modules,m", prog::value<string>()->implicit_value(
""),
137 "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).")
140 prog::options_description config(
"Configuration");
142 (
"steering", prog::value<string>(),
"The python steering file to run.")
143 (
"arg", prog::value<vector<string> >(&arguments),
"Additional arguments to be passed to the steering file")
144 (
"log_level,l", prog::value<string>(),
145 "Set global log level (one of DEBUG, INFO, RESULT, WARNING, or ERROR). Takes precedence over set_log_level() in steering file.")
146 (
"random-seed", prog::value<string>(),
147 "Set the default initial seed for the random number generator. "
148 "This does not take precedence over calls to set_random_seed() in the steering file, but just changes the default. "
149 "If no seed is set via either of these mechanisms, the initial seed will be taken from the system's entropy pool.")
150 (
"debug_level,d", prog::value<unsigned int>(),
"Set default debug level. Also sets the log level to DEBUG.")
151 (
"events,n", prog::value<unsigned int>(),
"Override number of events for EventInfoSetter; otherwise set maximum number of events.")
152 (
"run", prog::value<int>(),
"Override run for EventInfoSetter, must be used with -n and --experiment")
153 (
"experiment", prog::value<int>(),
"Override experiment for EventInfoSetter, must be used with -n and --run")
154 (
"skip-events", prog::value<unsigned int>(),
155 "Override skipNEvents for EventInfoSetter and RootInput. Skips this many events before starting.")
156 (
"input,i", prog::value<vector<string> >(),
157 "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.")
158 (
"sequence,S", prog::value<vector<string> >(),
159 "Override the number sequence (e.g. 23:42,101) defining the entries (starting from 0) which are processed by RootInput."
160 "Must be specified exactly once for each file to be opened."
161 "This means one sequence per input file AFTER wildcard expansion."
162 "The first event has the number 0.")
163 (
"output,o", prog::value<string>(),
164 "Override name of output file for (Seq)RootOutput. In case multiple modules are present in the path, only the first will be affected.")
165 (
"processes,p", prog::value<int>(),
"Override number of worker processes (>=1 enables, 0 disables parallel processing)");
167 prog::options_description advanced(
"Advanced Options");
168 advanced.add_options()
169 (
"module-io", prog::value<string>(),
170 "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'.")
171 (
"visualize-dataflow",
"Generate data flow diagram (dataflow.dot) for the executed steering file.")
173 "Disable collection of statistics during event processing. Useful for very high-rate applications, but produces empty table with 'print(statistics)'.")
175 "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.")
176 (
"dump-path", prog::value<string>(),
177 "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.")
178 (
"execute-path", prog::value<string>(),
179 "Do not read any provided steering file, instead execute the pickled (serialized) path from the given file.")
181 "Use ZMQ for multiprocessing instead of a RingBuffer. This has many implications and should only be used by experts.")
182 (
"job-information", prog::value<string>(),
183 "Create json file with metadata of output files and basf2 execution status.")
185 (
"profile", prog::value<string>(),
186 "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.")
190 prog::options_description cmdlineOptions;
191 cmdlineOptions.add(
generic).add(config).add(advanced);
193 prog::positional_options_description posOptDesc;
194 posOptDesc.add(
"steering", 1);
195 posOptDesc.add(
"arg", -1);
197 prog::variables_map varMap;
198 prog::store(prog::command_line_parser(argc, argv).
199 options(cmdlineOptions).positional(posOptDesc).run(), varMap);
200 prog::notify(varMap);
203 if (varMap.count(
"help")) {
204 cout <<
"Usage: " << argv[0] <<
" [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
205 cout << cmdlineOptions << endl;
207 }
else if (varMap.count(
"version")) {
208 pythonFile =
"basf2/version.py";
209 }
else if (varMap.count(
"info")) {
210 pythonFile =
"basf2_cli/print_info.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>();
232 B2INFO(
"Steering file: " << pythonFile);
238 if (varMap.count(
"processes")) {
239 int nprocesses = varMap[
"processes"].as<
int>();
240 if (nprocesses < 0) {
241 B2FATAL(
"Invalid number of processes!");
243 Environment::Instance().setNumberProcessesOverride(nprocesses);
247 if (varMap.count(
"zmq")) {
248 Environment::Instance().setUseZMQ(
true);
253 if (varMap.count(
"profile")) {
254 string profileModule = varMap[
"profile"].as<
string>();
256 if (!RUNNING_ON_VALGRIND) {
258 cout <<
"Profiling requested, restarting using callgrind" << endl;
264 const vector<string> valgrind_argv {
265 "valgrind",
"--tool=callgrind",
"--instr-atstart=no",
"--trace-children=no",
266 "--callgrind-out-file=callgrind." + profileModule +
".%p",
269 cmd.reserve(valgrind_argv.size());
270 for (
const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
272 for (
int i = 0; i < argc; ++i) { cmd.push_back(argv[i]); }
274 cmd.push_back(
nullptr);
278 if (execvp(cmd[0], cmd.data()) == -1) {
280 perror(
"Problem calling valgrind");
286 Environment::Instance().setProfileModuleName(profileModule);
288 Environment::Instance().setNumberProcessesOverride(0);
293 if (varMap.count(
"events")) {
294 unsigned int nevents = varMap[
"events"].as<
unsigned int>();
295 if (nevents == 0 or nevents == std::numeric_limits<unsigned int>::max()) {
296 B2FATAL(
"Invalid number of events (valid range: 1.." << std::numeric_limits<unsigned int>::max() - 1 <<
")!");
298 Environment::Instance().setNumberEventsOverride(nevents);
301 if (varMap.count(
"experiment") or varMap.count(
"run")) {
302 if (!varMap.count(
"events"))
303 B2FATAL(
"--experiment and --run must be used with --events/-n!");
304 if (!(varMap.count(
"run") and varMap.count(
"experiment")))
305 B2FATAL(
"Both --experiment and --run must be specified!");
307 int run = varMap[
"run"].as<
int>();
308 int experiment = varMap[
"experiment"].as<
int>();
309 B2ASSERT(
"run must be >= 0!", run >= 0);
310 B2ASSERT(
"experiment must be >= 0!", experiment >= 0);
311 Environment::Instance().setRunExperimentOverride(run, experiment);
315 if (varMap.count(
"skip-events")) {
316 unsigned int skipevents = varMap[
"skip-events"].as<
unsigned int>();
317 Environment::Instance().setSkipEventsOverride(skipevents);
321 if (varMap.count(
"input")) {
322 const auto& names = varMap[
"input"].as<vector<string>>();
323 Environment::Instance().setInputFilesOverride(names);
327 if (varMap.count(
"sequence")) {
328 const auto& sequences = varMap[
"sequence"].as<vector<string>>();
329 Environment::Instance().setEntrySequencesOverride(sequences);
333 if (varMap.count(
"output")) {
334 std::string name = varMap[
"output"].as<
string>();
335 Environment::Instance().setOutputFileOverride(name);
339 if (varMap.count(
"log_level")) {
340 std::string levelParam = varMap[
"log_level"].as<
string>();
342 for (
int i = LogConfig::c_Debug; i < LogConfig::c_Fatal; i++) {
344 if (boost::iequals(levelParam, thisLevel)) {
350 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
356 Environment::Instance().setLogLevelOverride(level);
360 if (varMap.count(
"debug_level")) {
361 unsigned int level = varMap[
"debug_level"].as<
unsigned int>();
362 LogSystem::Instance().getLogConfig()->setDebugLevel(level);
363 LogSystem::Instance().getLogConfig()->setLogLevel(LogConfig::c_Debug);
366 if (varMap.count(
"visualize-dataflow")) {
367 Environment::Instance().setVisualizeDataFlow(
true);
368 if (Environment::Instance().getNumberProcesses() > 0) {
369 B2WARNING(
"--visualize-dataflow cannot be used with parallel processing, no graphs will be saved!");
373 if (varMap.count(
"no-stats")) {
374 Environment::Instance().setNoStats(
true);
377 if (varMap.count(
"dry-run")) {
378 Environment::Instance().setDryRun(
true);
381 if (varMap.count(
"dump-path")) {
382 Environment::Instance().setPicklePath(varMap[
"dump-path"].as<string>());
385 if (varMap.count(
"random-seed")) {
386 RandomNumbers::initialize(varMap[
"random-seed"].as<string>());
389 if (varMap.count(
"job-information")) {
390 string jobInfoFile = varMap[
"job-information"].as<
string>();
391 MetadataService::Instance().setJsonFileName(jobInfoFile);
392 B2INFO(
"Job information file: " << jobInfoFile);
396 }
catch (exception& e) {
397 cerr <<
"error: " <<
e.what() << endl;
400 cerr <<
"Exception of unknown type!" << endl;
407 if (!pythonFile.empty()) {
409 if (!boost::filesystem::exists(pythonFile)) {
410 std::string libFile = FileSystem::findFile((libPath / pythonFile).
string(),
true);
411 if (!libFile.empty())
412 pythonFile = libFile;
420 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
421 std::vector<wstring> pyArgvString(arguments.size() + 1);
423 if (!pythonFile.empty()) {
424 pyArgvString[0] = converter.from_bytes(pythonFile);
426 pyArgvString[0] = converter.from_bytes(argv[0]);
428 for (
size_t i = 0; i < arguments.size(); i++) {
429 pyArgvString[i + 1] = converter.from_bytes(arguments[i]);
431 std::vector<const wchar_t*> pyArgvArray(pyArgvString.size());
432 for (
size_t i = 0; i < pyArgvString.size(); ++i) {
433 pyArgvArray[i] = pyArgvString[i].c_str();
436 PySys_SetArgv(pyArgvArray.size(),
const_cast<wchar_t**
>(pyArgvArray.data()));
439 executePythonFile(pythonFile);
445 if (!runModuleIOVisualization.empty()) {
446 DataFlowVisualization::executeModuleAndCreateIOPlot(runModuleIOVisualization);
450 if (Environment::Instance().getDryRun()) {
451 Environment::Instance().printJobInformation();
455 MetadataService::Instance().addBasf2Status(
"finished successfully");
456 MetadataService::Instance().finishBasf2();
457 }
catch (error_already_set&) {