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/filesystem.hpp>
31 #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 = boost::filesystem::system_complete(boost::filesystem::path(pythonFile));
74 if ((!(boost::filesystem::is_directory(fullPath))) && (boost::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 boost::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).")
174 (
"profile", prog::value<string>(),
175 "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.")
179 prog::options_description cmdlineOptions;
180 cmdlineOptions.add(
generic).add(config).add(advanced);
182 prog::positional_options_description posOptDesc;
183 posOptDesc.add(
"steering", 1);
184 posOptDesc.add(
"arg", -1);
186 prog::variables_map varMap;
187 prog::store(prog::command_line_parser(argc, argv).
188 options(cmdlineOptions).positional(posOptDesc).run(), varMap);
189 prog::notify(varMap);
192 if (varMap.count(
"help")) {
193 cout <<
"Usage: " << argv[0] <<
" [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
194 cout << cmdlineOptions << endl;
196 }
else if (varMap.count(
"version")) {
197 pythonFile =
"basf2/version.py";
198 }
else if (varMap.count(
"version-short")) {
199 pythonFile =
"basf2/version_short.py";
200 }
else if (varMap.count(
"info")) {
201 pythonFile =
"basf2_cli/print_info.py";
202 }
else if (varMap.count(
"license")) {
203 pythonFile =
"basf2_cli/print_license.py";
204 }
else if (varMap.count(
"modules")) {
205 string modArgs = varMap[
"modules"].as<
string>();
206 if (!modArgs.empty()) {
207 arguments.insert(arguments.begin(), modArgs);
212 if (varMap.count(
"steering")) {
213 arguments.insert(arguments.begin(), varMap[
"steering"].as<
string>());
215 pythonFile =
"basf2_cli/modules.py";
216 }
else if (varMap.count(
"module-io")) {
217 runModuleIOVisualization = varMap[
"module-io"].as<
string>();
218 pythonFile =
"basf2/core.py";
219 }
else if (varMap.count(
"execute-path")) {
220 Environment::Instance().setPicklePath(varMap[
"execute-path"].as<string>());
221 pythonFile =
"basf2_cli/execute_pickled_path.py";
222 }
else if (varMap.count(
"steering")) {
224 pythonFile = varMap[
"steering"].as<
string>();
225 B2INFO(
"Steering file: " << pythonFile);
231 if (varMap.count(
"processes")) {
232 int nprocesses = varMap[
"processes"].as<
int>();
233 if (nprocesses < 0) {
234 B2FATAL(
"Invalid number of processes!");
236 Environment::Instance().setNumberProcessesOverride(nprocesses);
240 if (varMap.count(
"zmq")) {
241 Environment::Instance().setUseZMQ(
true);
246 if (varMap.count(
"profile")) {
247 string profileModule = varMap[
"profile"].as<
string>();
249 if (!RUNNING_ON_VALGRIND) {
251 cout <<
"Profiling requested, restarting using callgrind" << endl;
257 const vector<string> valgrind_argv {
258 "valgrind",
"--tool=callgrind",
"--instr-atstart=no",
"--trace-children=no",
259 "--callgrind-out-file=callgrind." + profileModule +
".%p",
262 cmd.reserve(valgrind_argv.size());
263 for (
const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
265 for (
int i = 0; i < argc; ++i) { cmd.push_back(argv[i]); }
267 cmd.push_back(
nullptr);
271 if (execvp(cmd[0], cmd.data()) == -1) {
273 perror(
"Problem calling valgrind");
279 Environment::Instance().setProfileModuleName(profileModule);
281 Environment::Instance().setNumberProcessesOverride(0);
286 if (varMap.count(
"events")) {
287 unsigned int nevents = varMap[
"events"].as<
unsigned int>();
288 if (nevents == 0 or nevents == std::numeric_limits<unsigned int>::max()) {
289 B2FATAL(
"Invalid number of events (valid range: 1.." << std::numeric_limits<unsigned int>::max() - 1 <<
")!");
291 Environment::Instance().setNumberEventsOverride(nevents);
294 if (varMap.count(
"experiment") or varMap.count(
"run")) {
295 if (!varMap.count(
"events"))
296 B2FATAL(
"--experiment and --run must be used with --events/-n!");
297 if (!(varMap.count(
"run") and varMap.count(
"experiment")))
298 B2FATAL(
"Both --experiment and --run must be specified!");
300 int run = varMap[
"run"].as<
int>();
301 int experiment = varMap[
"experiment"].as<
int>();
302 B2ASSERT(
"run must be >= 0!", run >= 0);
303 B2ASSERT(
"experiment must be >= 0!", experiment >= 0);
304 Environment::Instance().setRunExperimentOverride(run, experiment);
308 if (varMap.count(
"skip-events")) {
309 unsigned int skipevents = varMap[
"skip-events"].as<
unsigned int>();
310 Environment::Instance().setSkipEventsOverride(skipevents);
314 if (varMap.count(
"input")) {
315 const auto& names = varMap[
"input"].as<vector<string>>();
316 Environment::Instance().setInputFilesOverride(names);
320 if (varMap.count(
"sequence")) {
321 const auto& sequences = varMap[
"sequence"].as<vector<string>>();
322 Environment::Instance().setEntrySequencesOverride(sequences);
326 if (varMap.count(
"output")) {
327 std::string name = varMap[
"output"].as<
string>();
328 Environment::Instance().setOutputFileOverride(name);
332 if (varMap.count(
"log_level")) {
333 std::string levelParam = varMap[
"log_level"].as<
string>();
335 for (
int i = LogConfig::c_Debug; i < LogConfig::c_Fatal; i++) {
337 if (boost::iequals(levelParam, thisLevel)) {
343 B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
349 Environment::Instance().setLogLevelOverride(level);
353 if (varMap.count(
"debug_level")) {
354 unsigned int level = varMap[
"debug_level"].as<
unsigned int>();
355 LogSystem::Instance().getLogConfig()->setDebugLevel(level);
356 LogSystem::Instance().getLogConfig()->setLogLevel(LogConfig::c_Debug);
359 if (varMap.count(
"visualize-dataflow")) {
360 Environment::Instance().setVisualizeDataFlow(
true);
361 if (Environment::Instance().getNumberProcesses() > 0) {
362 B2WARNING(
"--visualize-dataflow cannot be used with parallel processing, no graphs will be saved!");
366 if (varMap.count(
"no-stats")) {
367 Environment::Instance().setNoStats(
true);
370 if (varMap.count(
"dry-run")) {
371 Environment::Instance().setDryRun(
true);
374 if (varMap.count(
"dump-path")) {
375 Environment::Instance().setPicklePath(varMap[
"dump-path"].as<string>());
378 if (varMap.count(
"random-seed")) {
379 RandomNumbers::initialize(varMap[
"random-seed"].as<string>());
382 if (varMap.count(
"job-information")) {
383 string jobInfoFile = varMap[
"job-information"].as<
string>();
384 MetadataService::Instance().setJsonFileName(jobInfoFile);
385 B2INFO(
"Job information file: " << jobInfoFile);
388 if (varMap.count(
"realm")) {
389 std::string realmParam = varMap[
"realm"].as<
string>();
391 for (
int i = LogConfig::c_Online; i <= LogConfig::c_Production; i++) {
393 if (boost::iequals(realmParam, thisRealm)) {
399 B2FATAL(
"Invalid realm! Needs to be one of online or production.");
405 }
catch (exception& e) {
406 cerr <<
"error: " << e.what() << endl;
409 cerr <<
"Exception of unknown type!" << endl;
416 if (!pythonFile.empty()) {
418 if (!boost::filesystem::exists(pythonFile)) {
419 std::string libFile = FileSystem::findFile((libPath / pythonFile).
string(),
true);
420 if (!libFile.empty())
421 pythonFile = libFile;
429 std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
430 std::vector<wstring> pyArgvString(arguments.size() + 1);
432 if (!pythonFile.empty()) {
433 pyArgvString[0] = converter.from_bytes(pythonFile);
435 pyArgvString[0] = converter.from_bytes(argv[0]);
437 for (
size_t i = 0; i < arguments.size(); i++) {
438 pyArgvString[i + 1] = converter.from_bytes(arguments[i]);
440 std::vector<const wchar_t*> pyArgvArray(pyArgvString.size());
441 for (
size_t i = 0; i < pyArgvString.size(); ++i) {
442 pyArgvArray[i] = pyArgvString[i].c_str();
445 PySys_SetArgv(pyArgvArray.size(),
const_cast<wchar_t**
>(pyArgvArray.data()));
448 executePythonFile(pythonFile);
454 if (!runModuleIOVisualization.empty()) {
455 DataFlowVisualization::executeModuleAndCreateIOPlot(runModuleIOVisualization);
459 if (Environment::Instance().getDryRun()) {
460 Environment::Instance().printJobInformation();
464 MetadataService::Instance().addBasf2Status(
"finished successfully");
465 MetadataService::Instance().finishBasf2();
466 }
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.