Project
Loading...
Searching...
No Matches
ConfigurableParam.h
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12// first version 8/2018, Sandro Wenzel
13
14#ifndef COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_
15#define COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_
16
17#include <algorithm>
18#include <cassert>
19#include <cctype>
20#include <concepts>
21#include <cstdint>
22#include <limits>
23#include <map>
24#include <sstream>
25#include <stdexcept>
26#include <string>
27#include <type_traits>
28#include <unordered_map>
29#include <vector>
30#include <boost/property_tree/ptree_fwd.hpp>
31#include <typeinfo>
32#include <iostream>
33#include <array>
34
35class TFile;
36class TRootIOCtor;
37class TDataMember;
38
39namespace o2
40{
41namespace conf
42{
43// Base class for a configurable parameter.
44//
45// A configurable parameter (ConfigurableParameter) is a simple class, defining
46// a few (pod) properties/members which are registered
47// in a global (boost) property tree / structure.
48//
49// The features that we provide here are:
50// *) Automatic translation from C++ data description to INI/JSON/XML
51// format via ROOT introspection and boost property trees and
52// the possibility to readably save the configuration
53// *) Serialization/Deserialization into ROOT binary blobs (for the purpose
54// of writing/retrieving parameters from CCDB and to pass parameters along the processing chain)
55// *) Automatic integration of sub-classes into a common configuration
56// *) Be able to query properties from high level interfaces (just knowing
57// *) Be able to set properties from high-level interfaces (and modifying the underlying
58// C++ object)
59// *) Automatic ability to modify parameters from the command-line
60// *) Keeping track of the provenance of individual parameter values; The user is able
61// to see whether is parameter is defaulted-code-value/coming-from-CCDB/coming-from-comandline
62//
63// Note that concrete parameter sub-classes **must** be implemented
64// by inheriting from ConfigurableParamHelper and not from this class.
65//
66// ---------------------
67// Example: To define a parameter class TPCGasParameters, one does the following:
68//
69// class TPCGasParamer : public ConfigurableParamHelper<TPCGasParameter>
70// {
71// public:
72// double getGasDensity() const { return mGasDensity; }
73// private: // define properties AND default values
74// double mGasDensity = 1.23;
75// int mGasMaterialID = 1;
76//
77// O2ParamDef(TPCGasParameter, TPCGas); // a macro implementing some magic
78// }
79//
80//
81// We can now query the parameters in various ways
82// - All parameter classes are singletons and we can say: TPCGasParameter::Instance().getGasDensity();
83// - We can query by key (using classname + parameter name) from the global registry:
84// - ConfigurableParameter::getValueAs<double>("TPCGas", "mGasDensity");
85//
86// We can modify the parameters via the global registry together with an automatic syncing
87// of the underlying C++ object:
88// - ConfigurableParameter::setValue("TPCGas.mGasDensity", "0.5");
89//
90// - TPCGasParameter::Instance().getGasParameter() will now return 0.5;
91//
92// This feature allows to easily modify parameters at run-time via a textual representation
93// (for example by giving strings on the command line)
94//
95// The collection of all parameter keys and values can be stored to a human/machine readable
96// file
97// - ConfigurableParameter::writeJSON("thisconfiguration.json")
98
100 std::vector<std::pair<std::string, int>> vvalues;
101
102 bool isLegal(const std::string& value) const
103 {
104 for (auto& v : vvalues) {
105 if (v.first == value) {
106 return true;
107 }
108 }
109 return false;
110 }
111
112 bool isLegal(int value) const
113 {
114 for (auto& v : vvalues) {
115 if (v.second == value) {
116 return true;
117 }
118 }
119 return false;
120 }
121
122 std::string toString() const;
123 int getIntValue(const std::string& value) const;
124};
125
127{
128 public:
129 void add(const std::string& key, const TDataMember* dm);
130
131 bool contains(const std::string& key) const
132 {
133 return entries.count(key) > 0;
134 }
135
136 std::string toString() const;
137
138 const EnumLegalValues* operator[](const std::string& key) const
139 {
140 auto iter = entries.find(key);
141 return iter != entries.end() ? &iter->second : nullptr;
142 }
143
144 private:
145 std::unordered_map<std::string, EnumLegalValues> entries;
146};
147
148template <typename T>
149concept Container = !std::is_same_v<std::remove_cvref_t<T>, std::string> && requires(T t) {
150 typename T::value_type;
151 typename T::iterator;
152 { t.begin() } -> std::same_as<typename T::iterator>;
153 { t.end() } -> std::same_as<typename T::iterator>;
154};
155
156template <typename T>
157concept MapLike = Container<T> && requires {
158 typename T::key_type;
159 typename T::mapped_type;
160};
161
162template <typename T>
164
165template <typename>
166inline constexpr bool AlwaysFalse = false;
167
169{
170 public:
171 template <typename T>
172 static T parse(const std::string& str)
173 {
174 if constexpr (MapLike<T>) {
175 return parseMap<T>(str);
176 } else if constexpr (Container<T>) {
177 // Covers vector/list/deque as well as set/unordered_set: parseSequence
178 // inserts at end(), which both sequence and associative-set containers
179 // accept. (Any non-map Container is a SequenceContainer, so the previous
180 // separate parseSet branch was unreachable and re-parsed into a temporary
181 // vector first.)
182 return parseSequence<T>(str);
183 } else {
184 return parseScalar<T>(str);
185 }
186 }
187
188 static std::string trim(const std::string& str)
189 {
190 auto start = str.find_first_not_of(" \t\n\r\f\v");
191 if (start == std::string::npos) {
192 return "";
193 }
194 auto end = str.find_last_not_of(" \t\n\r\f\v");
195 return str.substr(start, end - start + 1);
196 }
197
198 private:
199 // Parse sequence and set containers (vector, list, deque, set, unordered_set)
200 template <SequenceContainer SequenceT>
201 static SequenceT parseSequence(const std::string& str)
202 {
203 SequenceT result;
204 using ValueType = typename SequenceT::value_type;
205 std::string cleaned = str;
206 if (!cleaned.empty() && cleaned.front() == '[' && cleaned.back() == ']') { // removed brackets [1,2,3] -> 1,2,3
207 cleaned = cleaned.substr(1, cleaned.length() - 2);
208 }
209 if (cleaned.empty() || cleaned == "{}") { // nothing to do
210 return result;
211 }
212 if constexpr (Container<ValueType>) {
213 static_assert(AlwaysFalse<ValueType>, "Nested containers are not supported as configurable parameters");
214 }
215 auto tokens = split(cleaned, ',');
216 for (const auto& token : tokens) {
217 std::string trimmed = trim(token);
218 result.insert(result.end(), parseScalar<ValueType>(trimmed));
219 }
220 return result;
221 }
222
223 // Parse map, unordered_map, multimap
224 template <MapLike MapT>
225 static MapT parseMap(const std::string& str)
226 {
227 MapT result;
228 using KeyType = typename MapT::key_type;
229 using ValueType = typename MapT::mapped_type;
230 std::string cleaned = str;
231 if (!cleaned.empty() && cleaned.front() == '{' && cleaned.back() == '}') { // stip braces {a:1,b:2} -> a:1,b:2
232 cleaned = cleaned.substr(1, cleaned.length() - 2);
233 }
234 if (cleaned.empty()) { // nothing to do
235 return result;
236 }
237 if constexpr (Container<KeyType> || Container<ValueType>) {
238 static_assert(AlwaysFalse<MapT>, "Nested containers are not supported as configurable parameters");
239 }
240 auto pairs = split(cleaned, ',');
241 for (const auto& pair_str : pairs) {
242 auto kv = split(pair_str, ':');
243 if (kv.size() != 2) {
244 throw std::runtime_error("Invalid map syntax: " + pair_str + ". Expected 'key:value' format, got ");
245 }
246 KeyType key = parseScalar<KeyType>(trim(kv[0]));
247 result[key] = parseScalar<ValueType>(trim(kv[1]));
248 }
249 return result;
250 }
251
252 // Parse scalar types
253 template <typename T>
254 static T parseScalar(const std::string& str)
255 {
256 if constexpr (std::is_same_v<T, std::string>) {
257 return str;
258 } else if constexpr (std::is_same_v<T, bool>) {
259 std::string lower = str;
260 std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
261 if (lower == "true" || lower == "1") {
262 return true;
263 }
264 if (lower == "false" || lower == "0") {
265 return false;
266 }
267 throw std::runtime_error("Invalid boolean value: " + str);
268 } else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, signed char>) {
269 size_t pos = 0;
270 long long value = std::stoll(str, &pos);
271 if (pos != str.size()) {
272 throw std::runtime_error("Failed to parse '" + str + "' as char type");
273 }
274 if (value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max()) {
275 throw std::runtime_error("Value out of range for char type: " + str);
276 }
277 return static_cast<T>(value);
278 } else if constexpr (std::is_same_v<T, unsigned char>) {
279 size_t pos = 0;
280 unsigned long long value = std::stoull(str, &pos);
281 if (pos != str.size()) {
282 throw std::runtime_error("Failed to parse '" + str + "' as unsigned char type");
283 }
284 if (value > std::numeric_limits<T>::max()) {
285 throw std::runtime_error("Value out of range for unsigned char type: " + str);
286 }
287 return static_cast<T>(value);
288 } else if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>) {
289 if (!str.empty() && str.front() == '-') {
290 throw std::runtime_error("Value out of range for unsigned integer type: " + str);
291 }
292 size_t pos = 0;
293 unsigned long long value = std::stoull(str, &pos);
294 if (pos != str.size() || value > std::numeric_limits<T>::max()) {
295 throw std::runtime_error("Failed to parse '" + str + "' as unsigned integer type");
296 }
297 return static_cast<T>(value);
298 } else if constexpr (std::is_integral_v<T>) {
299 size_t pos = 0;
300 long long value = std::stoll(str, &pos);
301 if (pos != str.size() || value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max()) {
302 throw std::runtime_error("Failed to parse '" + str + "' as signed integer type");
303 }
304 return static_cast<T>(value);
305 } else if constexpr (std::is_floating_point_v<T>) {
306 size_t pos = 0;
307 long double value = std::stold(str, &pos);
308 if (pos != str.size()) {
309 throw std::runtime_error("Failed to parse '" + str + "' as floating point type");
310 }
311 return static_cast<T>(value);
312 } else {
313 std::istringstream iss(str);
314 T value;
315 iss >> value;
316 iss >> std::ws;
317 if (iss.fail() || !iss.eof()) {
318 throw std::runtime_error("Failed to parse '" + str + "' as " + typeid(T).name());
319 }
320 return value;
321 }
322 }
323
324 // Split respecting nested brackets and braces
325 static std::vector<std::string> split(const std::string& str, char delimiter)
326 {
327 std::vector<std::string> tokens;
328 std::string current;
329 int bracket_depth = 0;
330 int brace_depth = 0;
331 for (char c : str) {
332 if (c == '[') {
333 bracket_depth++;
334 } else if (c == ']') {
335 bracket_depth--;
336 } else if (c == '{') {
337 brace_depth++;
338 } else if (c == '}') {
339 brace_depth--;
340 } else if (c == delimiter && bracket_depth == 0 && brace_depth == 0) {
341 // Keep empty fields: a stray delimiter (e.g. "[1,,3]" or "key:") must
342 // surface as a parse error downstream rather than silently dropping an
343 // element. The empty-container case ("[]"/"{}") is handled by the
344 // callers before split() is ever reached.
345 tokens.push_back(current);
346 current.clear();
347 continue;
348 }
349 current += c;
350 }
351 tokens.push_back(current);
352 return tokens;
353 }
354};
355
357{
358 public:
360 kCODE /* from default code initialization */,
361 kCCDB /* overwritten from CCDB */,
362 kRT /* changed during runtime via API call setValue (for example command line) */
363 /* can add more modes here */
364 };
365
367 Changed, // param was successfully changed
368 Unchanged, // param was not changed: new value is the same as previous
369 Failed // failed to update param
370 };
371
372 static std::string toString(EParamProvenance p)
373 {
374 static std::array<std::string, 3> names = {"CODE", "CCDB", "RT"};
375 return names[(int)p];
376 }
377
378 // get the name of the configurable Parameter
379 virtual std::string getName() const = 0;
380
381 // print the current keys and values to screen (optionally with provenance information)
382 virtual void printKeyValues(bool showprov = true, bool useLogger = false, bool withPadding = false, bool showHash = false) const = 0;
383
384 // get a single size_t hash_value of this parameter (can be used as a checksum to see
385 // if object changed or different)
386 virtual size_t getHash() const = 0;
387
388 // return the provenance of the member key
389 virtual EParamProvenance getMemberProvenance(const std::string& key) const = 0;
390
391 static EParamProvenance getProvenance(const std::string& key);
392
393 static void printAllRegisteredParamNames();
394 static void printAllKeyValuePairs(bool useLogger = false);
395
396 static const std::string& getOutputDir() { return sOutputDir; }
397
398 static void setOutputDir(const std::string& d) { sOutputDir = d; }
399
400 static bool configFileExists(std::string const& filepath);
401
402 // writes a human readable JSON file of all parameters
403 static void writeJSON(std::string const& filename, std::string const& keyOnly = "");
404 // writes a human readable INI file of all parameters
405 static void writeINI(std::string const& filename, std::string const& keyOnly = "");
406
407 // writes a human readable INI or JSON file depending on the extension
408 static void write(std::string const& filename, std::string const& keyOnly = "");
409
410 // can be used instead of using API on concrete child classes
411 template <typename T>
412 static T getValueAs(std::string key)
413 {
414 return [](auto* tree, const std::string& key) -> T {
415 if (!sIsFullyInitialized) {
416 initialize();
417 }
418 return tree->template get<T>(key);
419 }(sPtree, key);
420 }
421
422 template <typename T>
423 static void setValue(std::string const& mainkey, std::string const& subkey, T x)
424 {
425 if (!sIsFullyInitialized) {
426 initialize();
427 }
428 return [&subkey, &x, &mainkey](auto* tree) -> void {
429 assert(tree);
430 try {
431 auto key = mainkey + "." + subkey;
432 if (tree->template get_optional<std::string>(key).is_initialized()) {
433 tree->put(key, x);
434 auto changed = updateThroughStorageMap(mainkey, subkey, typeid(T), (void*)&x);
435 if (changed != EParamUpdateStatus::Failed) {
436 sValueProvenanceMap->find(key)->second = kRT; // set to runtime
437 }
438 }
439 } catch (std::exception const& e) {
440 std::cerr << "Error in setValue (T) " << e.what() << "\n";
441 }
442 }(sPtree);
443 }
444
445 static void setProvenance(std::string const& mainkey, std::string const& subkey, EParamProvenance p)
446 {
447 if (!sIsFullyInitialized) {
448 std::cerr << "setProvenance was called on non-initialized ConfigurableParam\n";
449 return;
450 }
451 try {
452 auto key = mainkey + "." + subkey;
453 auto keyProv = sValueProvenanceMap->find(key);
454 if (keyProv != sValueProvenanceMap->end()) {
455 keyProv->second = p;
456 }
457 } catch (std::exception const& e) {
458 std::cerr << "Error in setProvenance (T) " << e.what() << "\n";
459 }
460 }
461
462 // specialized for std::string
463 // which means that the type will be converted internally
464 static void setValue(std::string const& key, std::string const& valuestring);
465 static void setEnumValue(const std::string&, const std::string&);
466 static void setArrayValue(const std::string&, const std::string&);
467 static void setContainerValue(const std::string&, const std::string&);
468 static bool isRegisteredContainerType(const std::string& typeName);
469 static void registerContainerType(const std::string& key, const std::string& typeName);
470 static std::string getRegisteredContainerType(const std::string& key);
471 static bool assignRegisteredContainer(const std::string& typeName, void* target, const void* source);
472 static bool areRegisteredContainersEqual(const std::string& typeName, const void* lhs, const void* rhs);
473 static std::string registeredContainerAsString(const std::string& typeName, const void* source);
474
475 // update the storagemap from a vector of key/value pairs, calling setValue for each pair
476 static void setValues(std::vector<std::pair<std::string, std::string>> const& keyValues);
477
478 // initializes the parameter database
479 static void initialize();
480
481 // create CCDB snapshot
482 static void toCCDB(std::string filename);
483 // load from (CCDB) snapshot
484 static void fromCCDB(std::string filename);
485
486 // allows to provide a string of key-values from which to update
487 // (certain) key-values
488 // propagates changes down to each registered configuration
489 // might be useful to get stuff from the command line
490 static void updateFromString(std::string const&);
491
492 // provide a path to a configuration file with ConfigurableParam key/values
493 // If nonempty comma-separated paramsList is provided, only those params will
494 // be updated, absence of data for any of requested params will lead to fatal
495 static void updateFromFile(std::string const&, std::string const& paramsList = "", bool unchangedOnly = false);
496
497 // interface for use from the CCDB API; allows to sync objects read from CCDB with the information
498 // stored in the registry; modifies given object as well as registry
499 virtual void syncCCDBandRegistry(void* obj) = 0;
500
501 protected:
502 // constructor is doing nothing else but
503 // registering the concrete parameters
505
506 friend std::ostream& operator<<(std::ostream& out, const ConfigurableParam& me);
507
508 static void initPropertyTree();
509 static EParamUpdateStatus updateThroughStorageMap(std::string, std::string, std::type_info const&, void*);
510 static EParamUpdateStatus updateThroughStorageMapWithConversion(std::string const&, std::string const&);
511
512 virtual ~ConfigurableParam() = default;
513
514 // fill property tree with the key-values from the sub-classes
515 virtual void putKeyValues(boost::property_tree::ptree*) = 0;
516 virtual void output(std::ostream& out) const = 0;
517
518 virtual void serializeTo(TFile*) const = 0;
519 virtual void initFrom(TFile*) = 0;
520
521 // static map keeping, for each configuration key, its memory location and type
522 // (internal use to easily sync updates, this is ok since parameter classes are singletons)
523 static std::map<std::string, std::pair<std::type_info const&, void*>>* sKeyToStorageMap;
524
525 // keep track of provenance of parameters and values
526 static std::map<std::string, ConfigurableParam::EParamProvenance>* sValueProvenanceMap;
527
528 // A registry of enum names and their allowed values
529 // (stored as a vector of pairs <enumValueLabel, enumValueInt>)
531
532 static std::string sOutputDir;
533
534 void setRegisterMode(bool b) { sRegisterMode = b; }
535 bool isInitialized() const { return sIsFullyInitialized; }
536
537 // friend class o2::ccdb::CcdbApi;
538 private:
539 // static registry for implementations of this type
540 static std::vector<ConfigurableParam*>* sRegisteredParamClasses;
541 // static property tree (stocking all key - value pairs from instances of type ConfigurableParam)
542 static boost::property_tree::ptree* sPtree;
543 static bool sIsFullyInitialized;
544 static bool sRegisterMode;
545};
546
547} // end namespace conf
548} // end namespace o2
549
550// a helper macro for boilerplate code in parameter classes
551#define O2ParamDef(classname, key) \
552 public: \
553 classname(TRootIOCtor*) {} \
554 classname(classname const&) = delete; \
555 \
556 private: \
557 static constexpr char const* const sKey = key; \
558 static classname sInstance; \
559 classname() = default; \
560 template <typename T> \
561 friend class o2::conf::ConfigurableParamHelper; \
562 template <typename T, typename P> \
563 friend class o2::conf::ConfigurableParamPromoter;
564
565// a helper macro to implement necessary symbols in source
566#define O2ParamImpl(classname) classname classname::sInstance;
567
568#endif /* COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_ */
uint16_t pos
Definition RawData.h:3
uint32_t c
Definition RawData.h:2
StringRef key
friend std::ostream & operator<<(std::ostream &out, const ConfigurableParam &me)
virtual ~ConfigurableParam()=default
virtual void printKeyValues(bool showprov=true, bool useLogger=false, bool withPadding=false, bool showHash=false) const =0
static EParamUpdateStatus updateThroughStorageMapWithConversion(std::string const &, std::string const &)
static std::string registeredContainerAsString(const std::string &typeName, const void *source)
virtual void serializeTo(TFile *) const =0
virtual std::string getName() const =0
static void setEnumValue(const std::string &, const std::string &)
static void writeINI(std::string const &filename, std::string const &keyOnly="")
static void setValues(std::vector< std::pair< std::string, std::string > > const &keyValues)
static bool configFileExists(std::string const &filepath)
virtual void putKeyValues(boost::property_tree::ptree *)=0
static std::map< std::string, std::pair< std::type_info const &, void * > > * sKeyToStorageMap
static bool areRegisteredContainersEqual(const std::string &typeName, const void *lhs, const void *rhs)
static void setProvenance(std::string const &mainkey, std::string const &subkey, EParamProvenance p)
static void registerContainerType(const std::string &key, const std::string &typeName)
static void updateFromFile(std::string const &, std::string const &paramsList="", bool unchangedOnly=false)
static void write(std::string const &filename, std::string const &keyOnly="")
static bool isRegisteredContainerType(const std::string &typeName)
static T getValueAs(std::string key)
static bool assignRegisteredContainer(const std::string &typeName, void *target, const void *source)
virtual size_t getHash() const =0
virtual void initFrom(TFile *)=0
virtual EParamProvenance getMemberProvenance(const std::string &key) const =0
static void setArrayValue(const std::string &, const std::string &)
static void setValue(std::string const &mainkey, std::string const &subkey, T x)
static std::map< std::string, ConfigurableParam::EParamProvenance > * sValueProvenanceMap
static EParamProvenance getProvenance(const std::string &key)
static const std::string & getOutputDir()
static void printAllKeyValuePairs(bool useLogger=false)
static void toCCDB(std::string filename)
static void writeJSON(std::string const &filename, std::string const &keyOnly="")
static void setOutputDir(const std::string &d)
static std::string toString(EParamProvenance p)
static EParamUpdateStatus updateThroughStorageMap(std::string, std::string, std::type_info const &, void *)
static void setContainerValue(const std::string &, const std::string &)
static EnumRegistry * sEnumRegistry
virtual void output(std::ostream &out) const =0
static std::string getRegisteredContainerType(const std::string &key)
virtual void syncCCDBandRegistry(void *obj)=0
static void fromCCDB(std::string filename)
static void updateFromString(std::string const &)
static T parse(const std::string &str)
static std::string trim(const std::string &str)
void add(const std::string &key, const TDataMember *dm)
std::string toString() const
bool contains(const std::string &key) const
const EnumLegalValues * operator[](const std::string &key) const
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum target
Definition glcorearb.h:1641
GLuint start
Definition glcorearb.h:469
constexpr bool AlwaysFalse
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::string filename()
bool isLegal(int value) const
bool isLegal(const std::string &value) const
std::vector< std::pair< std::string, int > > vvalues
int getIntValue(const std::string &value) const
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
const std::string str