20#include <framework/core/Environment.h> 
   21#include <framework/core/DataFlowVisualization.h> 
   22#include <framework/core/MetadataService.h> 
   23#include <framework/core/RandomNumbers.h> 
   24#include <framework/logging/Logger.h> 
   25#include <framework/logging/LogConfig.h> 
   26#include <framework/logging/LogSystem.h> 
   27#include <framework/utilities/FileSystem.h> 
   30#include <boost/program_options.hpp> 
   31#include <boost/algorithm/string/predicate.hpp>  
   44#include <valgrind/valgrind.h> 
   50namespace prog = boost::program_options;
 
   54  void checkPythonStatus(PyConfig& config, PyStatus& status)
 
   56    if (PyStatus_Exception(status)) {
 
   57      PyConfig_Clear(&config);
 
   58      Py_ExitStatusException(status);
 
   63int main(
int argc, 
char* argv[])
 
   68  if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
 
   69    B2FATAL(
"Cannot remove SIGPIPE signal handler");
 
   79  const char* belle2SubDir = getenv(
"BELLE2_SUBDIR");
 
   80  std::filesystem::path libPath = 
"lib";
 
   81  libPath /= belle2SubDir;
 
   83  string runModuleIOVisualization(
""); 
 
   84  vector<string> arguments;
 
   92    prog::options_description 
generic(
"Generic options (to be used instead of steering file)");
 
   94    (
"help,h", 
"Print this help")
 
   95    (
"version,v", 
"Print long and verbose version string")
 
   96    (
"version-short", 
"Print short version string")
 
   97    (
"info", 
"Print information about basf2")
 
   98    (
"license", 
"Print the short version of the basf2 license")
 
   99    (
"modules,m", prog::value<string>()->implicit_value(
""),
 
  100     "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).")
 
  103    prog::options_description config(
"Configuration");
 
  105    (
"steering", prog::value<string>(), 
"The python steering file to run.")
 
  106    (
"arg", prog::value<vector<string> >(&arguments), 
"Additional arguments to be passed to the steering file")
 
  107    (
"log_level,l", prog::value<string>(),
 
  108     "Set global log level (one of DEBUG, INFO, RESULT, WARNING, or ERROR). Takes precedence over set_log_level() in steering file.")
 
  109    (
"package_log_level", prog::value<vector<string> >(),
 
  110     "Set package log level. Can be specified multiple times to use more than one package. (Examples: 'klm:INFO or cdc:DEBUG:10') ")
 
  111    (
"module_log_level", prog::value<vector<string> >(),
 
  112     "Set module log level. Can be specified multiple times to use more than one package. (Examples: 'EventInfoSetter:INFO or CDCDigitizer:DEBUG:10') ")
 
  113    (
"random-seed", prog::value<string>(),
 
  114     "Set the default initial seed for the random number generator. " 
  115     "This does not take precedence over calls to set_random_seed() in the steering file, but just changes the default. " 
  116     "If no seed is set via either of these mechanisms, the initial seed will be taken from the system's entropy pool.")
 
  117    (
"debug_level,d", prog::value<unsigned int>(), 
"Set default debug level. Also sets the log level to DEBUG.")
 
  118    (
"events,n", prog::value<unsigned int>(), 
"Override number of events for EventInfoSetter; otherwise set maximum number of events.")
 
  119    (
"run", prog::value<int>(), 
"Override run for EventInfoSetter, must be used with -n and --experiment")
 
  120    (
"experiment", prog::value<int>(), 
"Override experiment for EventInfoSetter, must be used with -n and --run")
 
  121    (
"skip-events", prog::value<unsigned int>(),
 
  122     "Override skipNEvents for EventInfoSetter and RootInput. Skips this many events before starting.")
 
  123    (
"input,i", prog::value<vector<string> >(),
 
  124     "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.")
 
  125    (
"sequence,S", prog::value<vector<string> >(),
 
  126     "Override the number sequence (e.g. 23:42,101) defining the entries (starting from 0) which are processed by RootInput." 
  127     "Must be specified exactly once for each file to be opened." 
  128     "This means one sequence per input file AFTER wildcard expansion." 
  129     "The first event has the number 0.")
 
  130    (
"output,o", prog::value<string>(),
 
  131     "Override name of output file for (Seq)RootOutput. In case multiple modules are present in the path, only the first will be affected.")
 
  132    (
"processes,p", prog::value<int>(), 
"Override number of worker processes (>=1 enables, 0 disables parallel processing)");
 
  134    prog::options_description advanced(
"Advanced Options");
 
  135    advanced.add_options()
 
  136    (
"module-io", prog::value<string>(),
 
  137     "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'.")
 
  138    (
"visualize-dataflow", 
"Generate data flow diagram (dataflow.dot) for the executed steering file.")
 
  140     "Disable collection of statistics during event processing. Useful for very high-rate applications, but produces empty table with 'print(statistics)'.")
 
  142     "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.")
 
  143    (
"dump-path", prog::value<string>(),
 
  144     "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.")
 
  145    (
"execute-path", prog::value<string>(),
 
  146     "Do not read any provided steering file, instead execute the pickled (serialized) path from the given file.")
 
  148     "Use ZMQ for multiprocessing instead of a RingBuffer. This has many implications and should only be used by experts.")
 
  149    (
"job-information", prog::value<string>(),
 
  150     "Create json file with metadata of output files and basf2 execution status.")
 
  151    (
"realm", prog::value<string>(),
 
  152     "Set the realm of the basf2 execution (online or production).")
 
  153    (
"secondary-input", prog::value<vector<string>>(),
 
  154     "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.")
 
  156    (
"profile", prog::value<string>(),
 
  157     "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.")
 
  161    prog::options_description cmdlineOptions;
 
  162    cmdlineOptions.add(generic).add(config).add(advanced);
 
  164    prog::positional_options_description posOptDesc;
 
  165    posOptDesc.add(
"steering", 1);
 
  166    posOptDesc.add(
"arg", -1);
 
  168    prog::variables_map varMap;
 
  169    prog::store(prog::command_line_parser(argc, argv).
 
  170                options(cmdlineOptions).positional(posOptDesc).run(), varMap);
 
  171    prog::notify(varMap);
 
  174    if (varMap.count(
"help")) {
 
  175      cout << 
"Usage: " << argv[0] << 
" [OPTIONS] [STEERING_FILE] [-- [STEERING_FILE_OPTIONS]]\n";
 
  176      cout << cmdlineOptions << endl;
 
  178    } 
else if (varMap.count(
"version")) {
 
  179      pythonFile = 
"basf2/version.py";
 
  180    } 
else if (varMap.count(
"version-short")) {
 
  181      pythonFile = 
"basf2/version_short.py";
 
  182    } 
else if (varMap.count(
"info")) {
 
  183      pythonFile = 
"basf2_cli/print_info.py";
 
  184    } 
else if (varMap.count(
"license")) {
 
  185      pythonFile = 
"basf2_cli/print_license.py";
 
  186    } 
else if (varMap.count(
"modules")) {
 
  187      string modArgs = varMap[
"modules"].as<
string>();
 
  188      if (!modArgs.empty()) {
 
  189        arguments.insert(arguments.begin(), modArgs);
 
  194      if (varMap.count(
"steering")) {
 
  195        arguments.insert(arguments.begin(), varMap[
"steering"].as<
string>());
 
  197      pythonFile = 
"basf2_cli/modules.py";
 
  198    } 
else if (varMap.count(
"module-io")) {
 
  199      runModuleIOVisualization = varMap[
"module-io"].as<
string>();
 
  200      pythonFile = 
"basf2/core.py"; 
 
  201    } 
else if (varMap.count(
"execute-path")) {
 
  203      pythonFile = 
"basf2_cli/execute_pickled_path.py";
 
  204    } 
else if (varMap.count(
"steering")) {
 
  206      pythonFile = varMap[
"steering"].as<
string>();
 
  209      pythonFile = 
"interactive.py";
 
  212    if (!pythonFile.empty()) {
 
  214      if (!std::filesystem::exists(pythonFile)) {
 
  216        if (!libFile.empty())
 
  217          pythonFile = libFile;
 
  219      if (varMap.count(
"steering") and not varMap.count(
"modules")) {
 
  220        B2INFO(
"Steering file: " << pythonFile);
 
  226    if (varMap.count(
"processes")) {
 
  227      int nprocesses = varMap[
"processes"].as<
int>();
 
  228      if (nprocesses < 0) {
 
  229        B2FATAL(
"Invalid number of processes!");
 
  235    if (varMap.count(
"zmq")) {
 
  241    if (varMap.count(
"profile")) {
 
  242      string profileModule = varMap[
"profile"].as<
string>();
 
  244      if (!RUNNING_ON_VALGRIND) {
 
  246        cout << 
"Profiling requested, restarting using callgrind" << endl;
 
  252        const vector<string> valgrind_argv {
 
  253          "valgrind", 
"--tool=callgrind", 
"--instr-atstart=no", 
"--trace-children=no",
 
  254          "--callgrind-out-file=callgrind." + profileModule + 
".%p",
 
  257        cmd.reserve(valgrind_argv.size());
 
  258        for (
const auto& arg : valgrind_argv) { cmd.push_back(strdup(arg.c_str())); }
 
  260        for (
int i = 0; i < argc; ++i)  { cmd.push_back(argv[i]); }
 
  262        cmd.push_back(
nullptr);
 
  266        if (execvp(cmd[0], cmd.data()) == -1) {
 
  268          perror(
"Problem calling valgrind");
 
  281    if (varMap.count(
"events")) {
 
  282      unsigned int nevents = varMap[
"events"].as<
unsigned int>();
 
  283      if (nevents == 0 or nevents == std::numeric_limits<unsigned int>::max()) {
 
  284        B2FATAL(
"Invalid number of events (valid range: 1.." << std::numeric_limits<unsigned int>::max() - 1 << 
")!");
 
  289    if (varMap.count(
"experiment") or varMap.count(
"run")) {
 
  290      if (!varMap.count(
"events"))
 
  291        B2FATAL(
"--experiment and --run must be used with --events/-n!");
 
  292      if (!(varMap.count(
"run") and varMap.count(
"experiment")))
 
  293        B2FATAL(
"Both --experiment and --run must be specified!");
 
  295      int run = varMap[
"run"].as<
int>();
 
  296      int experiment = varMap[
"experiment"].as<
int>();
 
  297      B2ASSERT(
"run must be >= 0!", run >= 0);
 
  298      B2ASSERT(
"experiment must be >= 0!", experiment >= 0);
 
  303    if (varMap.count(
"skip-events")) {
 
  304      unsigned int skipevents = varMap[
"skip-events"].as<
unsigned int>();
 
  309    if (varMap.count(
"input")) {
 
  310      const auto& names = varMap[
"input"].as<vector<string>>();
 
  315    if (varMap.count(
"sequence")) {
 
  316      const auto& sequences = varMap[
"sequence"].as<vector<string>>();
 
  321    if (varMap.count(
"output")) {
 
  322      std::string name = varMap[
"output"].as<
string>();
 
  327    if (varMap.count(
"log_level")) {
 
  328      std::string levelParam = varMap[
"log_level"].as<
string>();
 
  332        if (boost::iequals(levelParam, thisLevel)) { 
 
  338        B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
 
  348    if (varMap.count(
"package_log_level")) {
 
  349      const auto& packLogList = varMap[
"package_log_level"].as<vector<string>>();
 
  350      const std::string delimiter = 
":";
 
  351      for (
const std::string& packLog : packLogList) {
 
  352        if (packLog.find(delimiter) == std::string::npos) {
 
  353          B2FATAL(
"In --package_log_level input " << packLog << 
", no colon detected. ");
 
  357        auto packageName = packLog.substr(0, packLog.find(delimiter));
 
  358        std::string logName = packLog.substr(packLog.find(delimiter) + delimiter.length(), packLog.length());
 
  360        if ((logName.find(
"DEBUG") != std::string::npos) && logName.length() > 5) {
 
  362            debugLevel = std::stoi(logName.substr(logName.find(delimiter) + delimiter.length(), logName.length()));
 
  363          } 
catch (std::exception& e) {
 
  364            B2WARNING(
"In --package_log_level, issue parsing debugLevel. Still setting log level to DEBUG.");
 
  373          if (boost::iequals(logName, thisLevel)) { 
 
  379          B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
 
  382        if ((logName == 
"DEBUG") && (debugLevel >= 0)) {
 
  391    if (varMap.count(
"module_log_level")) {
 
  392      const auto& moduleLogList = varMap[
"module_log_level"].as<vector<string>>();
 
  393      const std::string delimiter = 
":";
 
  394      for (
const std::string& moduleLog : moduleLogList) {
 
  395        if (moduleLog.find(delimiter) == std::string::npos) {
 
  396          B2FATAL(
"In --module_log_level input " << moduleLog << 
", no colon detected. ");
 
  400        auto moduleName = moduleLog.substr(0, moduleLog.find(delimiter));
 
  401        std::string moduleLogName = moduleLog.substr(moduleLog.find(delimiter) + delimiter.length(), moduleLog.length());
 
  402        int moduleDebugLevel = -1;
 
  403        if ((moduleLogName.find(
"DEBUG") != std::string::npos) && moduleLogName.length() > 5) {
 
  405            moduleDebugLevel = std::stoi(moduleLogName.substr(moduleLogName.find(delimiter) + delimiter.length(), moduleLogName.length()));
 
  406          } 
catch (std::exception& e) {
 
  407            B2WARNING(
"In --module_log_level, issue parsing debugLevel. Still setting log level to DEBUG.");
 
  409          moduleLogName = 
"DEBUG";
 
  412        int module_level = -1;
 
  416          if (boost::iequals(moduleLogName, moduleThisLevel)) { 
 
  421        if (module_level < 0) {
 
  422          B2FATAL(
"Invalid log level! Needs to be one of DEBUG, INFO, RESULT, WARNING, or ERROR.");
 
  425        if ((moduleLogName == 
"DEBUG") && (moduleDebugLevel >= 0)) {
 
  434    if (varMap.count(
"debug_level")) {
 
  435      unsigned int level = varMap[
"debug_level"].as<
unsigned int>();
 
  440    if (varMap.count(
"visualize-dataflow")) {
 
  443        B2WARNING(
"--visualize-dataflow cannot be used with parallel processing, no graphs will be saved!");
 
  447    if (varMap.count(
"no-stats")) {
 
  451    if (varMap.count(
"dry-run")) {
 
  455    if (varMap.count(
"dump-path")) {
 
  459    if (varMap.count(
"random-seed")) {
 
  463    if (varMap.count(
"job-information")) {
 
  464      string jobInfoFile = varMap[
"job-information"].as<
string>();
 
  466      B2INFO(
"Job information file: " << jobInfoFile);
 
  469    if (varMap.count(
"realm")) {
 
  470      std::string realmParam = varMap[
"realm"].as<
string>();
 
  474        if (boost::iequals(realmParam, thisRealm)) { 
 
  480        B2FATAL(
"Invalid realm! Needs to be one of online or production.");
 
  485    if (varMap.count(
"secondary-input")) {
 
  486      const auto& names = varMap[
"secondary-input"].as<vector<string>>();
 
  490  } 
catch (exception& e) {
 
  491    cerr << 
"error: " << e.what() << endl;
 
  494    cerr << 
"Exception of unknown type!" << endl;
 
  503  PyConfig_InitPythonConfig(&config);
 
  504  config.install_signal_handlers = 0;
 
  505  config.safe_path = 0;
 
  507  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
 
  509  std::vector<wstring> pyArgvString(arguments.size() + 2);
 
  512  pyArgvString[0] = L
"python3"; 
 
  513  pyArgvString[1] = converter.from_bytes(pythonFile);
 
  514  for (
size_t i = 0; i < arguments.size(); i++) {
 
  515    pyArgvString[i + 2] = converter.from_bytes(arguments[i]);
 
  517  std::vector<const wchar_t*> pyArgvArray(pyArgvString.size());
 
  518  for (
size_t i = 0; i < pyArgvString.size(); ++i) {
 
  519    pyArgvArray[i] = pyArgvString[i].c_str();
 
  524  status = PyConfig_SetArgv(&config, pyArgvArray.size(), 
const_cast<wchar_t**
>(pyArgvArray.data()));
 
  525  checkPythonStatus(config, status);
 
  527  status = Py_InitializeFromConfig(&config);
 
  528  checkPythonStatus(config, status);
 
  530  auto fullPath = std::filesystem::absolute(std::filesystem::path(pythonFile));
 
  532  if ((std::filesystem::is_directory(fullPath)) || !(std::filesystem::exists(fullPath))) {
 
  533    B2FATAL(
"The given filename and/or path is not valid: " + pythonFile);
 
  536  std::ifstream file(fullPath.string().c_str());
 
  537  std::stringstream buffer;
 
  538  buffer << file.rdbuf();
 
  540  int pyReturnValue = Py_RunMain();
 
  543  PyConfig_Clear(&config);
 
  547  if (!runModuleIOVisualization.empty()) {
 
  560  return pyReturnValue;
 
static void executeModuleAndCreateIOPlot(const std::string &module)
Create independent I/O graph for a single module (without requiring a steering file).
void setOutputFileOverride(const std::string &name)
Override output file name for modules.
void setProfileModuleName(const std::string &name)
Set the name of a module to be profiled.
void setSteering(const std::string &steering)
Sets the steering file content.
void setNumberEventsOverride(unsigned int nevents)
Override the number of events in run 1 for EventInfoSetter module.
void setRealm(LogConfig::ELogRealm realm)
Set the basf2 execution realm.
void setRunExperimentOverride(int run, int experiment)
Override run and experiment for EventInfoSetter.
void setNumberProcessesOverride(int nproc)
Override number of processes to run in parallel.
void setLogLevelOverride(int level)
Override global log level if != LogConfig::c_Default.
void printJobInformation() const
Print information on input/output files in current steering file, used by –dry-run.
void setEntrySequencesOverride(const std::vector< std::string > &sequences)
Override the number sequences (e.g.
static Environment & Instance()
Static method to get a reference to the Environment instance.
void setSecondaryInputFilesOverride(const std::vector< std::string > &names)
Override secondary input file names for modules.
void setUseZMQ(bool useZMQ)
Set the flag if ZMQ should be used instead of the RingBuffer multiprocessing implementation.
void setPicklePath(const std::string &path)
Sets the path to the file where the pickled path is stored.
void setSkipEventsOverride(unsigned int skipEvents)
Set skipNEvents override.
void setVisualizeDataFlow(bool on)
Whether to generate DOT files with data store inputs/outputs of each module.
void setInputFilesOverride(const std::vector< std::string > &names)
Override input file names for modules.
void setNoStats(bool noStats)
Disable collection of statistics during event processing.
void setDryRun(bool dryRun)
Read steering file, but do not start any actually start any event processing.
static std::string findFile(const std::string &path, bool silent=false)
Search for given file or directory in local or central release directory, and return absolute path if...
void setDebugLevel(int debugLevel)
Configure the debug messaging level.
static const char * logRealmToString(ELogRealm realm)
Converts a log realm type to a string.
ELogLevel
Definition of the supported log levels.
@ c_Debug
Debug: for code development.
@ c_Fatal
Fatal: for situations were the program execution can not be continued.
ELogRealm
Definition of the supported execution realms.
@ c_Online
Online data taking.
@ c_Production
Data production jobs.
void setLogLevel(ELogLevel logLevel)
Configure the log level.
static const char * logLevelToString(ELogLevel logLevelType)
Converts a log level type to a string.
LogConfig * getLogConfig()
Returns global log system configuration.
LogConfig & getModuleLogConfig(const std::string &module)
Get the log configuration for the module with the given name.
static LogSystem & Instance()
Static method to get a reference to the LogSystem instance.
LogConfig & getPackageLogConfig(const std::string &package)
Get the log configuration for the package with the given name.
static void initialize()
Initialize the random number generator with a unique random seed;.
Abstract base class for different kinds of events.