Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-01-09 09:30:03

0001 //
0002 // Created by xmei on 9/7/22.
0003 //
0004 
0005 #include "eicrecon_cli.h"
0006 
0007 #include <JANA/CLI/JBenchmarker.h>
0008 #include <JANA/CLI/JSignalHandler.h>
0009 #include <JANA/JApplication.h>
0010 #include <JANA/JVersion.h>
0011 #include <JANA/Services/JComponentManager.h>
0012 #include <algorithm>
0013 #include <cstdlib>
0014 #include <cstring>
0015 #include <filesystem>
0016 #include <iostream>
0017 #include <memory>
0018 #include <set>
0019 #include <sstream>
0020 #include <stdexcept>
0021 #include <string>
0022 #include <utility>
0023 
0024 #include "JANA/JEventSource.h"
0025 #include "JANA/JException.h"
0026 #include "JANA/Services/JParameterManager.h"
0027 #include "print_info.h"
0028 
0029 #define QUOTE(name) #name
0030 #define STR(macro) QUOTE(macro)
0031 
0032 #ifndef EICRECON_APP_VERSION
0033 #define EICRECON_APP_VERSION Error
0034 #endif
0035 
0036 #define EICRECON_APP_VERSION_STR STR(EICRECON_APP_VERSION)
0037 
0038 namespace jana {
0039 
0040 void PrintUsageOptions() {
0041   std::cout << "Options:" << std::endl;
0042   std::cout << "   -h   --help                  Display this message" << std::endl;
0043   std::cout << "   -v   --version               Display version information" << std::endl;
0044   std::cout << "   -j   --janaversion           Display JANA version information" << std::endl;
0045   std::cout << "   -c   --configs               Display configuration parameters" << std::endl;
0046   std::cout << "   -l   --loadconfigs <file>    Load configuration parameters from file"
0047             << std::endl;
0048   std::cout << "   -d   --dumpconfigs <file>    Dump configuration parameters to file" << std::endl;
0049   std::cout << "   -b   --benchmark             Run in benchmark mode" << std::endl;
0050   std::cout << "   -L   --list-factories        List all the factories without running"
0051             << std::endl;
0052   std::cout << "   -Pkey=value                  Specify a configuration parameter" << std::endl;
0053   std::cout << "   -Pplugin:param=value         Specify a parameter value for a plugin"
0054             << std::endl;
0055   std::cout << std::endl;
0056 
0057   std::cout << "   --list-default-plugins       List all the default plugins" << std::endl;
0058   std::cout << "   --list-available-plugins     List plugins at $JANA_PLUGIN_PATH and $EICrecon_MY"
0059             << std::endl;
0060   std::cout << std::endl;
0061 
0062   std::cout << "   -Pplugins=values             Comma-separated list of extra plugins to load"
0063             << std::endl;
0064   std::cout << "   -Pplugins_to_ignore=values   Comma-separated list of plugins to ignore"
0065             << std::endl;
0066 
0067   std::cout << std::endl << std::endl;
0068 }
0069 
0070 void PrintUsageExample() {
0071 
0072   std::cout << "Example:" << std::endl;
0073   std::cout << "    eicrecon -Pplugins=plugin1,plugin2,plugin3 -Pnthreads=8 infile.root"
0074             << std::endl;
0075   std::cout << "    eicrecon -Ppodio:print_type_table=1 infile.root" << std::endl << std::endl;
0076   std::cout << std::endl << std::endl;
0077 }
0078 
0079 void PrintUsage() {
0080   /// Prints jana.cc command-line options to stdout, for use by the CLI.
0081   /// This does not include JANA parameters, which come from
0082   /// JParameterManager::PrintParameters() instead.
0083 
0084   std::cout << std::endl;
0085   std::cout << "Usage:" << std::endl;
0086   std::cout << "    eicrecon [options] source1 source2 ..." << std::endl;
0087   std::cout << std::endl;
0088 
0089   std::cout << "Description:" << std::endl;
0090   std::cout << "    Command-line interface for running JANA plugins. This can be used to"
0091             << std::endl;
0092   std::cout << "    read in events and process them. Command-line flags control configuration"
0093             << std::endl;
0094   std::cout << "    while additional arguments denote input files, which are to be loaded and"
0095             << std::endl;
0096   std::cout << "    processed by the appropriate EventSource plugin." << std::endl;
0097   std::cout << std::endl;
0098 
0099   PrintUsageOptions();
0100   PrintUsageExample();
0101 }
0102 
0103 void PrintVersion() { std::cout << "EICrecon version: " << EICRECON_APP_VERSION_STR << std::endl; }
0104 
0105 void PrintJANAVersion() { std::cout << "JANA version: " << JVersion::GetVersion() << std::endl; }
0106 
0107 void PrintDefaultPlugins(std::vector<std::string> const& default_plugins) {
0108   std::cout << "\n List default plugins:\n\n";
0109   printPluginNames(default_plugins);
0110   std::cout << std::endl << std::endl;
0111 }
0112 
0113 void GetPluginNamesInDir(std::set<std::string>& plugin_names, std::string dir_str) {
0114   // Edge case handler: taking care of invalid and empty dirs
0115   if (!std::filesystem::is_directory(dir_str)) {
0116     return;
0117   }
0118   if (std::filesystem::is_empty(dir_str)) {
0119     return;
0120   }
0121 
0122   std::string full_path;
0123   std::string filename;
0124   for (const auto& entry : std::filesystem::directory_iterator(dir_str)) {
0125     full_path = std::string(entry.path()); // Example: "/usr/local/plugins/Tutorial.so"
0126     filename  = full_path.substr(full_path.find_last_of("/") + 1); // Example: "Tutorial.so"
0127     if (filename.substr(filename.size() - 3) == ".so") {
0128       std::string s = filename.substr(0, filename.size() - 3);
0129       //                std::cout << filename << "==> "  << s << std::endl;
0130       plugin_names.insert(s);
0131     }
0132   }
0133 }
0134 
0135 /// Get the plugin names by searching for files named as *.so under $JANA_PLUGIN_PATH and
0136 /// $EICrecon_MY/plugins.
0137 /// @note It does not guarantee any effectiveness of the plugins.
0138 void GetPluginNamesFromEnvPath(std::set<std::string>& plugin_names, const char* env_var) {
0139   std::string dir_path;
0140   std::string paths;
0141 
0142   const char* env_p = getenv(env_var); // NOLINT(concurrency-mt-unsafe)
0143   if (env_p != nullptr) {
0144     if (strcmp(env_var, "EICrecon_MY") == 0) {
0145       paths = std::string(env_p) + "/plugins";
0146     } else {
0147       paths = std::string(env_p);
0148     }
0149 
0150     std::stringstream envvar_ss(paths);
0151     while (getline(envvar_ss, dir_path, ':')) {
0152       GetPluginNamesInDir(plugin_names, dir_path);
0153     }
0154   }
0155 }
0156 
0157 std::vector<std::string> GetAvailablePluginNames(std::vector<std::string> const& default_plugins) {
0158   // Use set to remove duplicates.
0159   /// @note The plugins will be override if you use the same plugin name at different paths.
0160   std::set<std::string> set_plugin_name;
0161   for (std::string s : default_plugins) {
0162     set_plugin_name.insert(s);
0163   }
0164 
0165   jana::GetPluginNamesFromEnvPath(set_plugin_name, "JANA_PLUGIN_PATH");
0166   jana::GetPluginNamesFromEnvPath(set_plugin_name, "EICrecon_MY");
0167 
0168   std::vector<std::string> plugin_names(set_plugin_name.begin(), set_plugin_name.end());
0169   return plugin_names;
0170 }
0171 
0172 void PrintAvailablePlugins(std::vector<std::string> const& default_plugins) {
0173   std::cout << "\n List available plugins:\n\n";
0174   printPluginNames(GetAvailablePluginNames(default_plugins));
0175   std::cout << std::endl << std::endl;
0176 }
0177 
0178 bool HasPrintOnlyCliOptions(UserOptions& options, std::vector<std::string> const& default_plugins) {
0179   if (options.flags[jana::ShowUsage]) {
0180     jana::PrintUsage();
0181     return true;
0182   }
0183   if (options.flags[jana::ShowVersion]) {
0184     jana::PrintVersion();
0185     return true;
0186   }
0187   if (options.flags[jana::ShowJANAVersion]) {
0188     jana::PrintJANAVersion();
0189     return true;
0190   }
0191   if (options.flags[jana::ShowDefaultPlugins]) {
0192     jana::PrintDefaultPlugins(default_plugins);
0193     return true;
0194   }
0195   if (options.flags[jana::ShowAvailablePlugins]) {
0196     jana::PrintAvailablePlugins(default_plugins);
0197     return true;
0198   }
0199   return false;
0200 }
0201 
0202 /// Detect whether the cli params @param options.params contain
0203 /// "-Pplugins_to_ignore=...<erase_str>...". If true, delete @param erase_str from the original cli
0204 /// string "-Pplugins_to_ignore".
0205 bool HasExcludeDefaultPluginsInCliParams(UserOptions& options, const std::string erase_str) {
0206   auto has_ignore_plugins = options.params.find("plugins_to_ignore");
0207   if (has_ignore_plugins == options.params.end()) {
0208     return false;
0209   }
0210 
0211   // Has cli option "-Pplugins_to_ignore". Look for @param erase_str
0212   std::size_t pos = has_ignore_plugins->second.find(erase_str);
0213   if (pos == std::string::npos) { // does not contain @param erase_str
0214     return false;
0215   }
0216 
0217   // Detect @param flag_str. Delete flag_str from the original cli option.
0218   std::string ignore_str;
0219   if (erase_str.length() + pos ==
0220       has_ignore_plugins->second.length()) { // @param flag_str is at the end
0221     ignore_str = has_ignore_plugins->second.erase(pos, erase_str.length());
0222   } else { // erase "<flag_str>," from "-Pplugins_to_ignore".
0223     ignore_str = has_ignore_plugins->second.erase(pos, erase_str.length() + 1);
0224   }
0225   options.params["plugins_to_ignore"] = ignore_str;
0226   return true;
0227 }
0228 
0229 void AddAvailablePluginsToOptionParams(UserOptions& options,
0230                                        std::vector<std::string> const& default_plugins) {
0231 
0232   std::set<std::string> set_plugins;
0233   // Add the plugins at $EICrecon_MY/plugins.
0234   //        jana::GetPluginNamesFromEnvPath(set_plugins, "EICrecon_MY");  // disabled as we do not
0235   //        want to automatically add these
0236 
0237   std::string plugins_str; // the complete plugins list
0238   for (std::string s : set_plugins) {
0239     plugins_str += s + ",";
0240   }
0241 
0242   // Add the default plugins into the plugin set if there is no
0243   // "-Pplugins_to_ignore=default (exclude all default plugins)" option
0244   if (!HasExcludeDefaultPluginsInCliParams(options, "default")) {
0245     /// @note: The sequence of adding the default plugins matters.
0246     /// Have to keep the original sequence to not causing troubles.
0247     for (std::string s : default_plugins) {
0248       plugins_str += s + ",";
0249     }
0250   }
0251 
0252   // Insert other plugins in cli option "-Pplugins=pl1,pl2,..."
0253   auto has_cli_plugin_params = options.params.find("plugins");
0254   if (has_cli_plugin_params != options.params.end()) {
0255     plugins_str += has_cli_plugin_params->second;
0256     options.params["plugins"] = plugins_str;
0257   } else {
0258     options.params["plugins"] = plugins_str.substr(0, plugins_str.size() - 1); // exclude last ","
0259   }
0260 }
0261 
0262 JApplication* CreateJApplication(UserOptions& options) {
0263 
0264   auto* para_mgr =
0265       new JParameterManager(); // JApplication owns params_copy, does not own eventSources
0266 
0267   // Add the cli options based on the user inputs
0268   for (auto pair : options.params) {
0269     para_mgr->SetParameter(pair.first, pair.second);
0270   }
0271 
0272   // Shut down the [INFO] msg of adding plugins, printing cpu info
0273   if (options.flags[ListFactories]) {
0274     para_mgr->SetParameter("log:off", "JPluginLoader,JArrowProcessingController,JArrow");
0275   }
0276 
0277   if (options.flags[LoadConfigs]) {
0278     // If the user specified an external config file, we should definitely use that
0279     try {
0280       para_mgr->ReadConfigFile(options.load_config_file);
0281     } catch (JException& e) {
0282       std::cout << "Problem loading config file '" << options.load_config_file << "'. Exiting."
0283                 << std::endl
0284                 << std::endl;
0285       exit(-1);
0286     }
0287     std::cout << "Loaded config file '" << options.load_config_file << "'." << std::endl
0288               << std::endl;
0289   }
0290 
0291   // If the user hasn't specified a timeout (on cmd line or in config file), set the timeout to
0292   // something reasonably high
0293   if (para_mgr->FindParameter("jana:timeout") == nullptr) {
0294     para_mgr->SetParameter("jana:timeout", 180);        // seconds
0295     para_mgr->SetParameter("jana:warmup_timeout", 180); // seconds
0296   }
0297 
0298   auto* app = new JApplication(para_mgr);
0299 
0300   const char* env_p = getenv("EICrecon_MY");
0301   if (env_p != nullptr) {
0302     app->AddPluginPath(std::string(env_p) + "/plugins");
0303   }
0304 
0305   for (auto event_src : options.eventSources) {
0306     app->Add(event_src);
0307   }
0308   return app;
0309 }
0310 
0311 void AddDefaultPluginsToJApplication(JApplication* app,
0312                                      std::vector<std::string> const& default_plugins) {
0313   for (std::string s : default_plugins) {
0314     app->AddPlugin(s);
0315   }
0316 }
0317 
0318 void PrintFactories(JApplication* app) {
0319   std::cout << std::endl << "List all the factories:" << std::endl << std::endl;
0320   printFactoryTable(app->GetComponentSummary());
0321   std::cout << std::endl;
0322 }
0323 
0324 void PrintPodioCollections(JApplication* app) {
0325   if (app->GetJParameterManager()->Exists("PODIO:PRINT_TYPE_TABLE")) {
0326     bool print_type_table = app->GetParameterValue<bool>("podio:print_type_table");
0327 
0328     // cli criteria: Ppodio:print_type_table=1
0329     if (print_type_table) {
0330       auto event_sources = app->GetService<JComponentManager>()->get_evt_srces();
0331       for (auto* event_source : event_sources) {
0332         //                    std::cout << event_source->GetPluginName() << std::endl;  // podio.so
0333         //                    std::cout << event_source->GetResourceName() << std::endl;
0334         if (event_source->GetPluginName().find("podio") != std::string::npos) {
0335           event_source->DoOpen();
0336         }
0337       }
0338     }
0339   }
0340 }
0341 
0342 void PrintConfigParameters(JApplication* app) {
0343   /// Print a table of the currently defined configuration parameters.
0344   /// n.b. this mostly duplicates a call to app->GetJParameterManager()->PrintParameters()
0345   /// but avoids the issue it has of setting the values column to same
0346   /// width for all parameters. (That leads to lots of whitespace being
0347   /// printed due to the very long podio:output_include_collections param.
0348 
0349   // Determine column widths
0350   auto params                    = app->GetJParameterManager()->GetAllParameters();
0351   std::size_t max_key_length     = 0;
0352   std::size_t max_val_length     = 0;
0353   std::size_t max_max_val_length = 32; // maximum width allowed for column.
0354   for (auto& [key, p] : params) {
0355     max_key_length = std::max(key.length(), max_key_length);
0356     if (p->GetValue().length() > max_val_length) {
0357       if (p->GetValue().length() <= max_max_val_length) {
0358         max_val_length = p->GetValue().length();
0359       }
0360     }
0361   }
0362 
0363   std::cout << "\nConfiguration Parameters:" << std::endl;
0364   std::cout << "Name" + std::string(std::max(max_key_length, std::size_t(4)) - 4, ' ') << " : ";
0365   std::cout << "Value" + std::string(std::max(max_val_length, std::size_t(5)) - 5, ' ') << " : ";
0366   std::cout << "Description" << std::endl;
0367   std::cout << std::string(max_key_length + max_val_length + 20, '-') << std::endl;
0368   for (auto& [key, p] : params) {
0369     std::stringstream ss;
0370     int key_length_diff = max_key_length - key.length();
0371     if (key_length_diff > 0) {
0372       ss << std::string(key_length_diff, ' ');
0373     }
0374     ss << key;
0375     ss << " | ";
0376 
0377     int val_length_diff = max_val_length - p->GetValue().length();
0378     if (val_length_diff > 0) {
0379       ss << std::string(val_length_diff, ' ');
0380     }
0381     ss << p->GetValue();
0382     ss << " | ";
0383     ss << p->GetDescription();
0384 
0385     std::cout << ss.str() << std::endl;
0386   }
0387   std::cout << std::string(max_key_length + max_val_length + 20, '-') << std::endl;
0388 }
0389 
0390 int Execute(JApplication* app, UserOptions& options) {
0391 
0392   std::cout << std::endl;
0393 
0394   if (options.flags[ShowConfigs]) {
0395     // Load all plugins, collect all parameters, exit without running anything
0396     app->Initialize();
0397     if (options.flags[Benchmark]) {
0398       JBenchmarker benchmarker(
0399           app); // Show benchmarking configs only if benchmarking mode specified
0400     }
0401     //            app->GetJParameterManager()->PrintParameters(true);
0402     PrintConfigParameters(app);
0403   } else if (options.flags[DumpConfigs]) {
0404     // Load all plugins, dump parameters to file, exit without running anything
0405     app->Initialize();
0406     std::cout << std::endl
0407               << "Writing configuration options to file: " << options.dump_config_file << std::endl;
0408     app->GetJParameterManager()->WriteConfigFile(options.dump_config_file);
0409   } else if (options.flags[Benchmark]) {
0410     JSignalHandler::register_handlers(app);
0411     // Run JANA in benchmark mode
0412     JBenchmarker benchmarker(app);  // Benchmarking params override default params
0413     benchmarker.RunUntilFinished(); // Benchmarker will control JApp Run/Stop
0414   } else if (options.flags[ListFactories]) {
0415     app->Initialize();
0416     PrintFactories(app);
0417 
0418     // TODO: more elegant processing here
0419     PrintPodioCollections(app);
0420   } else {
0421     if ((JVersion::GetMajorNumber() == 2) && (JVersion::GetMinorNumber() == 3) &&
0422         (JVersion::GetPatchNumber() <= 1)) {
0423       // JANA2 2.3.x has a bug with not filtering default-state parameters, which causes enormous printouts
0424       if (not app->GetJParameterManager()->Exists("jana:parameter_verbosity")) {
0425         app->GetJParameterManager()->SetParameter("jana:parameter_verbosity", 0);
0426       }
0427     }
0428     if (not app->GetJParameterManager()->Exists("jana:parameter_strictness")) {
0429       app->GetJParameterManager()->SetParameter("jana:parameter_strictness", 2);
0430     }
0431     // Run JANA in normal mode
0432     try {
0433       JSignalHandler::register_handlers(app);
0434       app->Run();
0435     } catch (JException& e) {
0436       std::cout << "----------------------------------------------------------" << std::endl;
0437       std::cout << e << std::endl;
0438       app->SetExitCode(EXIT_FAILURE);
0439     } catch (std::runtime_error& e) {
0440       std::cout << "----------------------------------------------------------" << std::endl;
0441       std::cout << "Exception: " << e.what() << std::endl;
0442       app->SetExitCode(EXIT_FAILURE);
0443     }
0444   }
0445   return app->GetExitCode();
0446 }
0447 
0448 UserOptions GetCliOptions(int nargs, char* argv[], bool expect_extra) {
0449 
0450   UserOptions options;
0451 
0452   std::map<std::string, Flag> tokenizer;
0453   tokenizer["-h"]                       = ShowUsage;
0454   tokenizer["--help"]                   = ShowUsage;
0455   tokenizer["-v"]                       = ShowVersion;
0456   tokenizer["--version"]                = ShowVersion;
0457   tokenizer["-j"]                       = ShowJANAVersion;
0458   tokenizer["--janaversion"]            = ShowJANAVersion;
0459   tokenizer["-c"]                       = ShowConfigs;
0460   tokenizer["--configs"]                = ShowConfigs;
0461   tokenizer["-l"]                       = LoadConfigs;
0462   tokenizer["--loadconfigs"]            = LoadConfigs;
0463   tokenizer["-d"]                       = DumpConfigs;
0464   tokenizer["--dumpconfigs"]            = DumpConfigs;
0465   tokenizer["-b"]                       = Benchmark;
0466   tokenizer["--benchmark"]              = Benchmark;
0467   tokenizer["-L"]                       = ListFactories;
0468   tokenizer["--list-factories"]         = ListFactories;
0469   tokenizer["--list-default-plugins"]   = ShowDefaultPlugins;
0470   tokenizer["--list-available-plugins"] = ShowAvailablePlugins;
0471 
0472   // `eicrecon` has the same effect with `eicrecon -h`
0473   if (nargs == 1) {
0474     options.flags[ShowUsage] = true;
0475   }
0476 
0477   for (int i = 1; i < nargs; i++) {
0478 
0479     std::string arg = argv[i];
0480     // std::cout << "Found arg " << arg << std::endl;
0481 
0482     if (argv[i][0] != '-') {
0483       options.eventSources.push_back(arg);
0484       continue;
0485     }
0486 
0487     switch (tokenizer[arg]) {
0488 
0489     case Benchmark:
0490       options.flags[Benchmark] = true;
0491       break;
0492 
0493     case ShowUsage:
0494       options.flags[ShowUsage] = true;
0495       break;
0496 
0497     case ShowVersion:
0498       options.flags[ShowVersion] = true;
0499       break;
0500 
0501     case ShowJANAVersion:
0502       options.flags[ShowJANAVersion] = true;
0503       break;
0504 
0505     case ShowConfigs:
0506       options.flags[ShowConfigs] = true;
0507       break;
0508 
0509     case LoadConfigs:
0510       options.flags[LoadConfigs] = true;
0511       if (i + 1 < nargs && argv[i + 1][0] != '-') {
0512         options.load_config_file = argv[i + 1];
0513         i += 1;
0514       } else {
0515         options.load_config_file = "jana.config";
0516       }
0517       break;
0518 
0519     case DumpConfigs:
0520       options.flags[DumpConfigs] = true;
0521       if (i + 1 < nargs && argv[i + 1][0] != '-') {
0522         options.dump_config_file = argv[i + 1];
0523         i += 1;
0524       } else {
0525         options.dump_config_file = "jana.config";
0526       }
0527       break;
0528 
0529     case ListFactories:
0530       options.flags[ListFactories] = true;
0531       break;
0532 
0533     case ShowDefaultPlugins:
0534       options.flags[ShowDefaultPlugins] = true;
0535       break;
0536 
0537     case ShowAvailablePlugins:
0538       options.flags[ShowAvailablePlugins] = true;
0539       break;
0540 
0541     // TODO: add exclude plugin options
0542     case Unknown:
0543       if (argv[i][0] == '-' && argv[i][1] == 'P') {
0544 
0545         std::size_t pos = arg.find("=");
0546         if ((pos != std::string::npos) && (pos > 2)) {
0547           std::string key = arg.substr(2, pos - 2);
0548           std::string val = arg.substr(pos + 1);
0549           if (options.params.find(key) != options.params.end()) {
0550             std::cout << "Duplicate parameter '" << arg << "' ignored" << std::endl;
0551           } else {
0552             options.params.insert({key, val});
0553           }
0554         } else {
0555           std::cout << "Invalid JANA parameter '" << arg << "': Expected format -Pkey=value"
0556                     << std::endl;
0557           options.flags[ShowConfigs] = true;
0558         }
0559       } else {
0560         if (!expect_extra) {
0561           std::cout << "Invalid command line flag '" << arg << "'" << std::endl;
0562           options.flags[ShowUsage] = true;
0563         }
0564       }
0565     }
0566   }
0567   return options;
0568 }
0569 } // namespace jana