Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:35

0001 
0002 // Copyright 2020, Jefferson Science Associates, LLC.
0003 // Subject to the terms in the LICENSE file found in the top-level directory.
0004 
0005 #include "JParameterManager.h"
0006 #include "JANA/JLogger.h"
0007 
0008 #include <sstream>
0009 #include <vector>
0010 #include <string>
0011 #include <fstream>
0012 #include <cstring>
0013 #include <JANA/Utils/JTablePrinter.h>
0014 
0015 using namespace std;
0016 
0017 
0018 /// @brief Default constructor
0019 JParameterManager::JParameterManager() {
0020     SetLoggerName("jana");
0021 }
0022 
0023 /// @brief Copy constructor
0024 /// @details Does a deep copy of the JParameter objects to avoid double frees.
0025 JParameterManager::JParameterManager(const JParameterManager& other) {
0026 
0027     m_logger = other.m_logger;
0028     // Do a deep copy of contained JParameters to avoid double frees
0029     for (const auto& param : other.m_parameters) {
0030         m_parameters.insert({param.first, new JParameter(*param.second)});
0031     }
0032 }
0033 
0034 /// @brief Destructor
0035 /// @details Destroys all contained JParameter objects.
0036 JParameterManager::~JParameterManager() {
0037     for (const auto& p : m_parameters) delete p.second;
0038     m_parameters.clear();
0039 }
0040 
0041 
0042 /// @brief Converts a parameter name to all lowercase
0043 ///
0044 /// @details  When accessing the m_parameters map, strings are always converted to
0045 ///           lower case. This makes configuration parameters case-insensitive in effect,
0046 ///           while still preserving the user's choice of case in the parameter name.
0047 std::string JParameterManager::ToLower(const std::string& name) {
0048     std::string tmp(name);
0049     std::transform(tmp.begin(), tmp.end(), tmp.begin(), ::tolower);
0050     return tmp;
0051 }
0052 
0053 
0054 /// @brief Test whether a parameter of some name exists
0055 ///
0056 /// @param [in] name    the parameter name
0057 /// @return             whether that parameter was found
0058 bool JParameterManager::Exists(string name) {
0059     return m_parameters.find(ToLower(name)) != m_parameters.end();
0060 }
0061 
0062 
0063 /// @brief Retrieves a JParameter object of some name
0064 ///
0065 /// @param [in] name    the parameter name
0066 /// @return             a pointer to the JParameter.
0067 ///
0068 /// @note The JParameter pointer is still owned by the JParameterManager, so don't delete it.
0069 JParameter* JParameterManager::FindParameter(std::string name) {
0070     auto result = m_parameters.find(ToLower(name));
0071     if (result == m_parameters.end()) {
0072         return nullptr;
0073     }
0074     return result->second;
0075 }
0076 
0077 void JParameterManager::PrintParameters() {
0078     // In an ideal world, these parameters would be declared in Init(). However, we always have the chicken-and-egg problem to contend with
0079     // when initializing ParameterManager and other Services. As long as PrintParameters() gets called at the end of JApplication::Initialize(),
0080     // this is fine.
0081     SetDefaultParameter("jana:parameter_verbosity", m_verbosity, "0: Don't show parameters table\n"
0082                                                                  "1: Show user-provided parameters only\n"
0083                                                                  "2: Show defaulted parameters\n"
0084                                                                  "3: Show defaulted advanced parameters");
0085     
0086     SetDefaultParameter("jana:parameter_strictness", m_strictness, "0: Ignore unused parameters\n"
0087                                                                    "1: Warn on unused parameters\n"
0088                                                                    "2: Throw on unused parameters");
0089 
0090     if ((m_verbosity < 0) || (m_verbosity > 3)) throw JException("jana:parameter_verbosity: Bad parameter value!");
0091     if ((m_strictness < 0) || (m_strictness > 2)) throw JException("jana:parameter_strictness: Bad parameter value!");
0092 
0093     PrintParameters(m_verbosity, m_strictness);
0094 }
0095 
0096 
0097 /// @brief Prints parameters to stdout
0098 ///
0099 /// @param [in] int verbosity   0: Don't show parameters table
0100 ///                             1: Show parameters with user-provided values only
0101 ///                             2: Show parameters with default values, except those marked "advanced"
0102 ///                             3: Show parameters with default values, including those marked "advanced"
0103 ///
0104 /// @param [in] int strictness  0: Ignore unused parameters
0105 ///                             1: Warn on unused parameters
0106 ///                             2: Throw on unused parameters
0107 void JParameterManager::PrintParameters(int verbosity, int strictness) {
0108 
0109     bool strictness_violation = false;
0110 
0111     // Check for unused and deprecated parameters first.
0112     // If we find a problem, warn about that separately, and also increase the parameter table verbosity to help the user debug
0113     for (auto& pair : m_parameters) {
0114         const auto& key = pair.first;
0115         auto param = pair.second;
0116 
0117         if ((strictness > 0) && (!param->IsDefault()) && (!param->IsUsed())) {
0118             strictness_violation = true;
0119             LOG_ERROR(m_logger) << "Parameter '" << key << "' appears to be unused. Possible typo?" << LOG_END;
0120         }
0121         if ((!param->IsDefault()) && (param->IsDeprecated())) {
0122             LOG_ERROR(m_logger) << "Parameter '" << key << "' has been deprecated and may no longer be supported in future releases." << LOG_END;
0123         }
0124     }
0125 
0126     if (strictness_violation) {
0127         LOG_WARN(m_logger) << "Printing parameter table with full verbosity due to strictness warning" << LOG_END;
0128         verbosity = 3; // Crank up verbosity before printing out table
0129     }
0130 
0131     if (verbosity == 0) {
0132         LOG_INFO(m_logger) << "Configuration parameters table hidden. Set jana:parameter_verbosity > 0 to view." << LOG_END;
0133         return;
0134     }
0135 
0136     // Filter table
0137     vector<JParameter*> params_to_print;
0138 
0139     for (auto& pair : m_parameters) {
0140         auto param = pair.second;
0141 
0142         if (param->IsDeprecated() && (param->IsDefault())) continue;
0143         // Always hide deprecated parameters that are NOT in use
0144         
0145         if ((verbosity == 1) && (param->IsDefault())) continue;
0146         // At verbosity level 1, hide all default-valued parameters
0147         
0148         if ((verbosity == 2) && (param->IsDefault()) && (param->IsAdvanced())) continue;
0149         // At verbosity level 2, hide only advanced default-valued parameters
0150         
0151         params_to_print.push_back(param);
0152     } 
0153 
0154     // If all params are set to default values, then print a one line summary and return
0155     if (params_to_print.empty()) {
0156         LOG_INFO(m_logger) << "All configuration parameters set to default values." << LOG_END;
0157         return;
0158     }
0159 
0160     LOG_WARN(m_logger) << "Configuration Parameters" << LOG_END;
0161     for (JParameter* p: params_to_print) {
0162 
0163         LOG_WARN(m_logger) << LOG_END;
0164         LOG_WARN(m_logger) << " - key:         " << p->GetKey() << LOG_END;
0165         if (!p->IsDefault()) {
0166             LOG_WARN(m_logger) << "   value:       " << p->GetValue() << LOG_END;
0167         }
0168         if (p->HasDefault()) {
0169             LOG_WARN(m_logger) << "   default:     " << p->GetDefault() << LOG_END;
0170         }
0171         if (!p->GetDescription().empty()) {
0172             std::istringstream iss(p->GetDescription());
0173             std::string line;
0174             bool is_first_line =  true;
0175             while (std::getline(iss, line)) {
0176                 if (is_first_line) {
0177                     LOG_INFO(m_logger) << "   description: " << line << LOG_END;
0178                 }
0179                 else {
0180                     LOG_INFO(m_logger) << "                " << line << LOG_END;
0181                 }
0182                 is_first_line = false;
0183             }
0184         }
0185         if (p->IsConflicted()) {
0186             LOG_WARN(m_logger) << "   warning:     Conflicting defaults" << LOG_END;
0187         }
0188         if (p->IsDeprecated()) {
0189             LOG_WARN(m_logger) << "   warning:     Deprecated" << LOG_END;
0190             // If deprecated, it no longer matters whether it is advanced or not. If unused, won't show up here anyway.
0191         }
0192         if (!p->IsUsed()) {
0193             // Can't be both deprecated and unused, since JANA only finds out that it is deprecated by trying to use it
0194             // Can't be both advanced and unused, since JANA only finds out that it is advanced by trying to use it
0195             LOG_WARN(m_logger) << "   warning:     Unused" << LOG_END;
0196         }
0197         if (p->IsAdvanced()) {
0198             LOG_WARN(m_logger) << "   warning:     Advanced" << LOG_END;
0199         }
0200     }
0201     LOG_WARN(m_logger) << LOG_END;
0202 
0203     // Now that we've printed the table, we can throw an exception if we are being super strict
0204     if (strictness_violation && strictness > 1) {
0205         throw JException("Unrecognized parameters found! Throwing an exception because jana:parameter_strictness=2. See full parameters table in log.");
0206     }
0207 }
0208 
0209 /// @brief Access entire map of parameters
0210 ///
0211 /// \return The parameter map
0212 ///
0213 /// @details Use this to do things like writing all parameters out to a non-standard format.
0214 /// This creates a copy of the map. Any modifications you make to the map itself won't propagate
0215 /// back to the JParameterManager. However, any modifications you make to the enclosed JParameters
0216 /// will. Prefer using SetParameter, SetDefaultParameter, FindParameter, or FilterParameters instead.
0217 std::map<std::string, JParameter*> JParameterManager::GetAllParameters() {
0218     return m_parameters;
0219 }
0220 
0221 /// @brief Load parameters from a configuration file
0222 ///
0223 /// @param filename    Path to the configuration file
0224 ///
0225 /// @details
0226 /// The file should have the form:
0227 ///     <pre>
0228 ///     key1 value1
0229 ///     key2 value2
0230 ///     ...
0231 ///     </pre>
0232 ///
0233 /// There should be a space between the key and the value. The key may contain no spaces.
0234 /// The value is taken as the rest of the line up to, but not including the newline.
0235 /// A key may be specified with no value and the value will be set to "1".
0236 /// A "#" charater will discard the remaining characters in a line up to
0237 /// the next newline. Lines starting with "#" are ignored completely.
0238 /// Lines with no characters (except for the newline) are ignored.
0239 ///
0240 void JParameterManager::ReadConfigFile(std::string filename) {
0241 
0242     // Try and open file
0243     ifstream ifs(filename);
0244 
0245     if (!ifs.is_open()) {
0246         LOG_ERROR(m_logger) << "Unable to open configuration file \"" << filename << "\" !" << LOG_END;
0247         throw JException("Unable to open configuration file");
0248     }
0249 
0250     // Loop over lines
0251     char line[8192];
0252     while (!ifs.eof()) {
0253         // Read in next line ignoring comments
0254         bzero(line, 8192);
0255         ifs.getline(line, 8190);
0256         if (strlen(line) == 0) continue;
0257         if (line[0] == '#') continue;
0258         string str(line);
0259 
0260         // Check for comment character and erase comment if found
0261         size_t comment_pos = str.find('#');
0262         if (comment_pos != string::npos) {
0263 
0264             // Parameter descriptions are automatically added to configuration dumps
0265             // by adding a space, then the '#'. For string parameters, this extra
0266             // space shouldn't be there so check for it and delete it as well if found.
0267             if (comment_pos > 0 && str[comment_pos - 1] == ' ')comment_pos--;
0268 
0269             str.erase(comment_pos);
0270         }
0271 
0272         // Break line into tokens
0273         vector<string> tokens;
0274         string buf; // Have a buffer string
0275         stringstream ss(str); // Insert the string into a stream
0276         while (ss >> buf)tokens.push_back(buf);
0277         if (tokens.size() < 1)continue; // ignore empty lines
0278 
0279         // Use first token as key
0280         string key = tokens[0];
0281 
0282         // Concatenate remaining tokens into val string
0283         string val = "";
0284         for (unsigned int i = 1; i < tokens.size(); i++) {
0285             if (i != 1)val += " ";
0286             val += tokens[i];
0287         }
0288         if (val == "")val = "1";
0289 
0290         // Override defaulted values only
0291         JParameter* param = FindParameter(key);
0292         if (param == nullptr || param->HasDefault()) {
0293             SetParameter(key, val);
0294         }
0295     }
0296 
0297     // close file
0298     ifs.close();
0299 }
0300 
0301 
0302 /// @brief Write parameters out to an ASCII configuration file
0303 ///
0304 /// @param [in] filename    Path to the configuration file
0305 ///
0306 /// @details The file is written in a format compatible with reading in via ReadConfigFile().
0307 ///
0308 void JParameterManager::WriteConfigFile(std::string filename) {
0309 
0310     // Try and open file
0311     ofstream ofs(filename);
0312     if (!ofs.is_open()) {
0313         LOG_ERROR(m_logger) << "Unable to open configuration file \"" << filename << "\" for writing!" << LOG_END;
0314         throw JException("Unable to open configuration file for writing!");
0315     }
0316 
0317     // Write header
0318     time_t t = time(NULL);
0319     string timestr(ctime(&t));
0320     ofs << "#" << endl;
0321     ofs << "# JANA configuration parameters" << endl;
0322     ofs << "# Created " << timestr;
0323     ofs << "#" << endl;
0324     ofs << endl;
0325 
0326     // Lock mutex to prevent manipulation of parameters while we're writing
0327     //_mutex.lock();
0328 
0329     // First, find the longest key and value
0330     size_t max_key_len = 0;
0331     size_t max_val_len = 0;
0332     for (auto pair : m_parameters) {
0333         auto key_len = pair.first.length();
0334         auto val_len = pair.second->GetValue().length();
0335         if (key_len > max_key_len) max_key_len = key_len;
0336         if (val_len > max_val_len) max_val_len = val_len;
0337     }
0338 
0339     // Loop over parameters a second time and print them out
0340     for (auto pair : m_parameters) {
0341         std::string key = pair.first;
0342         std::string val = pair.second->GetValue();
0343         std::string desc = pair.second->GetDescription();
0344 
0345         ofs << key << string(max_key_len - key.length(), ' ') << " "
0346             << val << string(max_val_len - val.length(), ' ') << " # "
0347             << desc << std::endl;
0348     }
0349     // Release mutex
0350     //_mutex.unlock();
0351 
0352     ofs.close();
0353 }
0354 
0355 
0356 /// @brief Copy a list of all parameters into the supplied map, replacing its current contents
0357 ///
0358 /// @param [in] filter        Empty by default. If the filter string is non-empty, it is used to filter
0359 ///                           out all parameters whose key does not start with the filter string.
0360 ///                           Furthermore, the filter string is removed from the keys.
0361 ///
0362 /// @param [out] parms        A map of {key: string, parameter: string}. The contents of this
0363 ///                           map are replaced on each call. Parameter values are returned
0364 ///                           as strings rather than as JParameter objects, which means we don't
0365 ///                           have to worry about ownership. Returned parameters are marked as used.
0366 ///
0367 void JParameterManager::FilterParameters(std::map<std::string, std::string> &parms, std::string filter) {
0368 
0369     parms.clear();
0370     std::lock_guard<std::mutex> lock(m_mutex);
0371     for (auto pair : m_parameters) {
0372         string key = pair.second->GetKey();  // Note that this is the version that preserves the original case!
0373         string value = pair.second->GetValue();
0374         if (filter.size() > 0) {
0375             if (key.substr(0, filter.size()) != filter) continue;
0376             key.erase(0, filter.size());
0377         }
0378         pair.second->SetIsUsed(true);
0379         parms[key] = value;
0380     }
0381 }
0382 
0383 JLogger JParameterManager::GetLogger(const std::string& component_prefix) {
0384 
0385     JLogger logger;
0386     logger.group = component_prefix;
0387 
0388     auto global_log_level = RegisterParameter("jana:global_loglevel", JLogger::Level::INFO, "Global log level");
0389 
0390     bool enable_timestamp = RegisterParameter("jana:log:show_timestamp", true, "Show timestamp in log output");
0391     auto enable_threadstamp = RegisterParameter("jana:log:show_threadstamp", false, "Show threadstamp in log output");
0392     auto enable_group = RegisterParameter("jana:log:show_group", false, "Show threadstamp in log output");
0393     auto enable_level = RegisterParameter("jana:log:show_level", true, "Show threadstamp in log output");
0394 
0395     if (component_prefix.empty()) {
0396         logger.level = global_log_level;
0397     }
0398     else {
0399         std::ostringstream os;
0400         os << component_prefix << ":loglevel";
0401         logger.level = RegisterParameter(os.str(), global_log_level, "Component log level");
0402     }
0403     logger.ShowLevel(enable_level);
0404     logger.ShowTimestamp(enable_timestamp);
0405     logger.ShowThreadstamp(enable_threadstamp);
0406     logger.ShowGroup(enable_group);
0407     logger.show_threadstamp = enable_threadstamp;
0408     return logger;
0409 }
0410 
0411