Belle II Software  release-05-01-25
basf2.cc
1 /**************************************************************************
2  * BASF2 *
3  * *
4  * The Belle Analysis Software Framework 2 *
5  * *
6  * *
7  * There are two ways to work with the framework. Either *
8  * by executing "basf2" and providing a python steering *
9  * file as an argument or by using the framework within *
10  * python itself. *
11  * *
12  * This file implements the main executable "basf2". *
13  * *
14  * *
15  * Copyright(C) 2010-2016 Belle II Collaboration *
16  * *
17  * Contributing authors : *
18  * (main framework) *
19  * Andreas Moll *
20  * Martin Heck *
21  * Nobu Katayama *
22  * Ryosuke Itoh *
23  * Thomas Kuhr *
24  * Kolja Prothmann *
25  * Martin Ritter *
26  * Zbynek Drasal *
27  * Christian Pulvermacher *
28  * Thomas Keck *
29  * *
30  * This software is provided "as is" without any warranty. *
31  **************************************************************************/
32 
33 #include <boost/python.hpp> //Has to be the first include (restriction due to python)
34 
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>
43 
44 #include <boost/program_options.hpp>
45 #include <boost/filesystem.hpp>
46 #include <boost/algorithm/string/predicate.hpp> //for iequals()
47 
48 #include <csignal>
49 #include <cstdlib>
50 #include <iostream>
51 #include <algorithm>
52 #include <string>
53 #include <vector>
54 #include <fstream>
55 #include <locale>
56 #include <codecvt>
57 
58 #ifdef HAS_CALLGRIND
59 #include <valgrind/valgrind.h>
60 #endif
61 
62 using namespace std;
63 using namespace Belle2;
64 using namespace boost::python;
65 
66 namespace prog = boost::program_options;
67 
68 namespace {
69  void executePythonFile(const string& pythonFile)
70  {
71  // temporarily disable users' rootlogon
72  // FIXME: remove this line when ROOT-10468 is resolved
73  import("ROOT").attr("PyConfig").attr("DisableRootLogon") = true;
74 
75  object main_module = import("__main__");
76  object main_namespace = main_module.attr("__dict__");
77  if (pythonFile.empty()) {
78  // No steering file given, start an interactive ipython session
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);
85  return;
86  }
87  // otherwise execute the steering file
88  auto fullPath = boost::filesystem::system_complete(boost::filesystem::path(pythonFile));
89  if ((!(boost::filesystem::is_directory(fullPath))) && (boost::filesystem::exists(fullPath))) {
90 
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);
96  } else {
97  B2FATAL("The given filename and/or path is not valid: " + pythonFile);
98  }
99  }
100 }
101 
102 int main(int argc, char* argv[])
103 {
104  //remove SIGPIPE handler set by ROOT which sometimes caused infinite loops
105  //See https://savannah.cern.ch/bugs/?97991
106  //default action is to abort
107  if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
108  B2FATAL("Cannot remove SIGPIPE signal handler");
109  }
110 
111  //Initialize metadata service
112  MetadataService::Instance();
113 
114  //Check for Belle2 environment variables (during environment initialisation)
115  Environment::Instance();
116 
117  //Get the lib path (checked for NULL in Environment)
118  const char* belle2SubDir = getenv("BELLE2_SUBDIR");
119  boost::filesystem::path libPath = "lib";
120  libPath /= belle2SubDir;
121 
122  string runModuleIOVisualization(""); //nothing done if empty
123  vector<string> arguments;
124  string pythonFile;
125 
126  try {
127  //---------------------------------------------------
128  // Handle command line options
129  //---------------------------------------------------
130 
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).")
138  ;
139 
140  prog::options_description config("Configuration");
141  config.add_options()
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)");
166 
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.")
172  ("no-stats",
173  "Disable collection of statistics during event processing. Useful for very high-rate applications, but produces empty table with 'print(statistics)'.")
174  ("dry-run",
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.")
180  ("zmq",
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.")
184 #ifdef HAS_CALLGRIND
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.")
187 #endif
188  ;
189 
190  prog::options_description cmdlineOptions;
191  cmdlineOptions.add(generic).add(config).add(advanced);
192 
193  prog::positional_options_description posOptDesc;
194  posOptDesc.add("steering", 1);
195  posOptDesc.add("arg", -1);
196 
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);
201 
202  //Check for non-steering file options
203  if (varMap.count("help")) {
204  cout << "Usage: " << argv[0] << " [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
205  cout << cmdlineOptions << endl;
206  return 0;
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);
215  }
216  // recent boost program_options will not consume extra tokens for
217  // implicit options. In this case the module/package name gets consumed
218  // in the steering file so we just use that.
219  if (varMap.count("steering")) {
220  arguments.insert(arguments.begin(), varMap["steering"].as<string>());
221  }
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"; //make module maps available, visualization will happen later
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")) {
230  // steering file not misused as module name, so print it's name :D
231  pythonFile = varMap["steering"].as<string>();
232  B2INFO("Steering file: " << pythonFile);
233  }
234 
235 
236  // -p
237  // Do now so that we can override if profiling is requested
238  if (varMap.count("processes")) {
239  int nprocesses = varMap["processes"].as<int>();
240  if (nprocesses < 0) {
241  B2FATAL("Invalid number of processes!");
242  }
243  Environment::Instance().setNumberProcessesOverride(nprocesses);
244  }
245 
246  // --zmq
247  if (varMap.count("zmq")) {
248  Environment::Instance().setUseZMQ(true);
249  }
250 
251 
252 #ifdef HAS_CALLGRIND
253  if (varMap.count("profile")) {
254  string profileModule = varMap["profile"].as<string>();
255  //We want to profile a module so check if we are running under valgrind
256  if (!RUNNING_ON_VALGRIND) {
257  //Apparently not. Ok, let's call ourself using valgrind
258  cout << "Profiling requested, restarting using callgrind" << endl;
259 
260  //Sadly calling processes in C++ is very annoying as we have to
261  //build a command line.
262  vector<char*> cmd;
263  //First we add all valgrind arguments.
264  const vector<string> valgrind_argv {
265  "valgrind", "--tool=callgrind", "--instr-atstart=no", "--trace-children=no",
266  "--callgrind-out-file=callgrind." + profileModule + ".%p",
267  };
268  //As execvp wants non-const char* pointers we have to copy the string contents.
269  cmd.reserve(valgrind_argv.size());
270  for (const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
271  //And now we add our own arguments, including the program name.
272  for (int i = 0; i < argc; ++i) { cmd.push_back(argv[i]); }
273  //Finally, execvp wants a nullptr as last argument
274  cmd.push_back(nullptr);
275  //And call this thing. Execvp will not return if successful as the
276  //current process will be replaced so we do not need to care about what
277  //happens if succesful
278  if (execvp(cmd[0], cmd.data()) == -1) {
279  int errsv = errno;
280  perror("Problem calling valgrind");
281  return errsv;
282  }
283  }
284  //Ok, running under valgrind, set module name we want to profile in
285  //environment.
286  Environment::Instance().setProfileModuleName(profileModule);
287  //and make sure there is no multiprocessing when profiling
288  Environment::Instance().setNumberProcessesOverride(0);
289  }
290 #endif
291 
292  // -n
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 << ")!");
297  }
298  Environment::Instance().setNumberEventsOverride(nevents);
299  }
300  // --run & --experiment
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!");
306 
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);
312  }
313 
314  // --skip-events
315  if (varMap.count("skip-events")) {
316  unsigned int skipevents = varMap["skip-events"].as<unsigned int>();
317  Environment::Instance().setSkipEventsOverride(skipevents);
318  }
319 
320  // -i
321  if (varMap.count("input")) {
322  const auto& names = varMap["input"].as<vector<string>>();
323  Environment::Instance().setInputFilesOverride(names);
324  }
325 
326  // -S
327  if (varMap.count("sequence")) {
328  const auto& sequences = varMap["sequence"].as<vector<string>>();
329  Environment::Instance().setEntrySequencesOverride(sequences);
330  }
331 
332  // -o
333  if (varMap.count("output")) {
334  std::string name = varMap["output"].as<string>();
335  Environment::Instance().setOutputFileOverride(name);
336  }
337 
338  // -l
339  if (varMap.count("log_level")) {
340  std::string levelParam = varMap["log_level"].as<string>();
341  int level = -1;
342  for (int i = LogConfig::c_Debug; i < LogConfig::c_Fatal; i++) {
343  std::string thisLevel = LogConfig::logLevelToString((LogConfig::ELogLevel)i);
344  if (boost::iequals(levelParam, thisLevel)) { //case-insensitive
345  level = i;
346  break;
347  }
348  }
349  if (level < 0) {
350  B2FATAL("Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
351  }
352 
353  //set log level
354  LogSystem::Instance().getLogConfig()->setLogLevel((LogConfig::ELogLevel)level);
355  //and make sure it takes precedence overy anything in the steeering file
356  Environment::Instance().setLogLevelOverride(level);
357  }
358 
359  // -d
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);
364  }
365 
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!");
370  }
371  }
372 
373  if (varMap.count("no-stats")) {
374  Environment::Instance().setNoStats(true);
375  }
376 
377  if (varMap.count("dry-run")) {
378  Environment::Instance().setDryRun(true);
379  }
380 
381  if (varMap.count("dump-path")) {
382  Environment::Instance().setPicklePath(varMap["dump-path"].as<string>());
383  }
384 
385  if (varMap.count("random-seed")) {
386  RandomNumbers::initialize(varMap["random-seed"].as<string>());
387  }
388 
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);
393  }
394 
395 
396  } catch (exception& e) {
397  cerr << "error: " << e.what() << endl;
398  return 1;
399  } catch (...) {
400  cerr << "Exception of unknown type!" << endl;
401  return 1;
402  }
403 
404  //---------------------------------------------------
405  // If the python file is set, execute it
406  //---------------------------------------------------
407  if (!pythonFile.empty()) {
408  //Search in local or central lib/ if this isn't a direct path
409  if (!boost::filesystem::exists(pythonFile)) {
410  std::string libFile = FileSystem::findFile((libPath / pythonFile).string(), true);
411  if (!libFile.empty())
412  pythonFile = libFile;
413  }
414  }
415 
416  try {
417  //Init Python interpreter
418  Py_InitializeEx(0);
419 
420  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
421  std::vector<wstring> pyArgvString(arguments.size() + 1);
422  // Set argument 0 to either script name or the basf2 exectuable
423  if (!pythonFile.empty()) {
424  pyArgvString[0] = converter.from_bytes(pythonFile);
425  } else {
426  pyArgvString[0] = converter.from_bytes(argv[0]);
427  }
428  for (size_t i = 0; i < arguments.size(); i++) {
429  pyArgvString[i + 1] = converter.from_bytes(arguments[i]);
430  }
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();
434  }
435  //Pass python filename and additional arguments to python
436  PySys_SetArgv(pyArgvArray.size(), const_cast<wchar_t**>(pyArgvArray.data()));
437 
438  //Execute Python file
439  executePythonFile(pythonFile);
440 
441  //Finish Python interpreter
442  Py_Finalize();
443 
444  //basf2.py was loaded, now do module I/O visualization
445  if (!runModuleIOVisualization.empty()) {
446  DataFlowVisualization::executeModuleAndCreateIOPlot(runModuleIOVisualization);
447  }
448 
449  //--dry-run: print gathered information
450  if (Environment::Instance().getDryRun()) {
451  Environment::Instance().printJobInformation();
452  }
453 
454  //Report completion in json metadata
455  MetadataService::Instance().addBasf2Status("finished successfully");
456  MetadataService::Instance().finishBasf2();
457  } catch (error_already_set&) {
458  //Apparently an exception occured which wasn't handled. So print the traceback
459  PyErr_Print();
460  //And in rare cases, i.e. when redirecting output, the buffers are not
461  //flushed unless we finalize python. So do it now
462  Py_Finalize();
463  return 1;
464  }
465 
466  return 0;
467 }
prepareAsicCrosstalkSimDB.e
e
aux.
Definition: prepareAsicCrosstalkSimDB.py:53
Belle2::LogConfig::ELogLevel
ELogLevel
Definition of the supported log levels.
Definition: LogConfig.h:36
main
int main(int argc, char **argv)
Run all tests.
Definition: test_main.cc:77
Belle2
Abstract base class for different kinds of events.
Definition: MillepedeAlgorithm.h:19