diff --git a/src/backends/dlb/dlb_talp_tree/CMakeLists.txt b/src/backends/dlb/dlb_talp_tree/CMakeLists.txt index 160cc50b7d255384ca8ffc20e3c9efb884ee897b..fc44ff5ab47f2d21d7646835b9919ac1192cedbb 100644 --- a/src/backends/dlb/dlb_talp_tree/CMakeLists.txt +++ b/src/backends/dlb/dlb_talp_tree/CMakeLists.txt @@ -1 +1 @@ -target_sources(nesmik PRIVATE dlb_talp_tree.cpp) +target_sources(nesmik PRIVATE dlb_talp_tree.cpp dlb_talp_tree_json_serializer.cpp dlb_talp_tree_ascii_serializer.cpp) diff --git a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.cpp b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.cpp index 62c3e2d973ee5b781beb6aacfddce60454b54fc5..3bd604aafe19eb222be09da1cdd07b588339d224 100644 --- a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.cpp +++ b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.cpp @@ -1,64 +1,70 @@ #include "dlb_talp_tree.hpp" #include "dlb.h" #include "dlb_talp.h" -#include +#include "dlb_talp_tree_ascii_serializer.hpp" +#include "dlb_talp_tree_json_serializer.hpp" +#include +#include #include +#include +#include #include -#include #include -#include #ifdef WITH_MPI - #include -#endif - +#include +#endif #include -DLBTalpTreeStrategy::DLBTalpTreeStrategy():mpi_helper_{}{ - parallelism_descriptor_={ - .mpi_descriptor_ = MPIDescriptor::Aware, - .thread_descriptor_ = ThreadDescriptor::Unsupported - }; -} +using json = nlohmann::json; +DLBTalpTreeStrategy::DLBTalpTreeStrategy() : mpi_helper_{} { + parallelism_descriptor_ = {.mpi_descriptor_ = MPIDescriptor::Aware, + .thread_descriptor_ = + ThreadDescriptor::Unsupported}; +} +std::string region_hash_to_string(std::size_t region_hash) { + // We need to cast the std::size_t before conversion to get + // decimal representation in the string. + return std::to_string(static_cast(region_hash)); +} void DLBTalpTreeStrategy::RegionStart( - const ProperlyNestedRegionInformation ®ion) noexcept { + const ProperlyNestedRegionInformation ®ion) noexcept { // Construct the opening region hash by joining the name of the region with // the current region (i.e. the parent) hash. - std::string region_name = std::string(region.name) + std::to_string((unsigned int) this->current_region); - if (region.name.compare(this->top_region) == 0) { - region_name = this->top_region; + std::string region_name = + std::string(region.name) + region_hash_to_string(current_region_); + if (region.name.compare(top_region_) == 0) { + region_name = top_region_; } std::size_t region_hash = std::hash{}(region_name); - if (this->regions.count(region_hash) == 0) { + if (regions_.count(region_hash) == 0) { // Add the new region as a child to the current region - this->regions[this->current_region].childs.push_back(region_hash); + regions_[current_region_].childs.push_back(region_hash); // Add the new region in the map - this->regions[region_hash] = { - /* .name */ std::string(region.name), - /* .tree_lines */ "", - /* .parent */ this->current_region, - /* .childs */ std::vector() - }; + regions_[region_hash] = {/* .name */ std::string(region.name), + /* .parent */ current_region_, + /* .childs */ {}, + /* .pop_metrics*/ {}}; } - this->current_region = region_hash; + current_region_ = region_hash; - std::string name = std::to_string((unsigned int) region_hash); - this->talp_profiling_strategy.RegionStart({name}); + std::string name = region_hash_to_string(region_hash); + talp_profiling_strategy_.RegionStart({name}); } void DLBTalpTreeStrategy::RegionStopLast( - const ProperlyNestedRegionInformation ®ion) noexcept { + const ProperlyNestedRegionInformation ®ion) noexcept { - auto current_region = this->regions[this->current_region]; + auto current_region = regions_[current_region_]; if (region.name.compare(current_region.name) != 0) { std::stringstream error; @@ -66,166 +72,77 @@ void DLBTalpTreeStrategy::RegionStopLast( error << "neSmiK:: Closing " << region.name << " would make "; error << current_region.name << "not nested." << std::endl; - error << "neSmiK:: Closing " << current_region.name << " instead." << std::endl; + error << "neSmiK:: Closing " << current_region.name << " instead." + << std::endl; std::cerr << error.str(); } - std::string name = std::to_string((unsigned int)this->current_region); - if (current_region.name.compare(this->top_region) == 0) { - name = this->top_region; + std::string name = region_hash_to_string(current_region_); + if (current_region.name.compare(top_region_) == 0) { + name = top_region_; } - this->current_region = current_region.parent; + current_region_ = current_region.parent; - this->talp_profiling_strategy.RegionStop({name}); + talp_profiling_strategy_.RegionStop({name}); } +void DLBTalpTreeStrategy::Init() noexcept { - -void DLBTalpTreeStrategy::Init() noexcept { - - this->current_region = std::hash{}(this->top_region); - this->regions[this->current_region] = { - /* .name */ this->top_region, - /* .tree_lines */ "", - /* .parent */ 0, - /* .childs */ {}, - }; - this->talp_profiling_strategy.Init(); + current_region_ = std::hash{}(top_region_); + regions_[current_region_] = {/* .name */ top_region_, + /* .parent */ 0, + /* .childs */ {}, + /* .pop_metrics*/ {}}; + talp_profiling_strategy_.Init(); } -void DLBTalpTreeStrategy::Finalize() noexcept { - - this->talp_profiling_strategy.RegionStop({.name = this->top_region}); - - std::size_t max_width = 0; - - // First pass to format the names with the appropiate tree lines and to determine paddings. - { - std::size_t top_region = std::hash{}(this->top_region); - - std::stack regions_stack; - regions_stack.push(top_region); - - std::stack regions_level; - regions_level.push(0); - - std::stack regions_tree_lines; - regions_tree_lines.push(""); - - while (!regions_stack.empty()) { - auto ¤t_region = this->regions[regions_stack.top()]; - //TalpRegionNode ¤t_region = this->regions[regions_stack.top()]; - int level = regions_level.top(); - std::string tree_lines = regions_tree_lines.top(); +void DLBTalpTreeStrategy::Finalize() noexcept { - regions_stack.pop(); - regions_level.pop(); - regions_tree_lines.pop(); + // Stop the top region which was automatically started by TALP. + talp_profiling_strategy_.RegionStop({.name = top_region_}); - // I am the last child either if the stack is empty or if the level of the following element - // in the stack is lower than mine. - bool last_child = regions_level.empty() || regions_level.top() < level; - - std::string next_tree_lines = " │ "; // By default I get the continuation line. - if (level == 0) next_tree_lines = ""; // If I am the root region I don't need line. - else if (last_child) next_tree_lines = " "; // If I am the last child I add space. - - for (auto region : current_region.childs) { - regions_stack.push(region); - regions_level.push(level+1); - regions_tree_lines.push(tree_lines + next_tree_lines); - } - - if (level == 0) tree_lines += ""; // If I am the root I don't have lines - else if (last_child) tree_lines += " └─ "; // If I am the last child I close the line - else tree_lines += " ├─ "; // Otherwise i continue the line. - - current_region.tree_lines = tree_lines; - max_width = std::max(max_width, current_region.name.length() + - current_region.tree_lines.length()); - } - } - - std::size_t top_region = std::hash{}(this->top_region); - - std::stack regions_stack; - regions_stack.push(top_region); - - std::stringstream region_stream_stdout; - std::stringstream region_stream_csv; - - while (!regions_stack.empty()) { - std::size_t current_region_hash = regions_stack.top(); - auto current_region = this->regions[current_region_hash]; - regions_stack.pop(); - - for (auto region : current_region.childs) { - regions_stack.push(region); - } - - std::string name = std::to_string((unsigned int)current_region_hash); - if (current_region.name.compare(this->top_region) == 0) { - name = this->top_region; + // Collect the POP metrics from all the regions + for (auto ®ion : regions_) { + std::string name = region_hash_to_string(region.first); + if (region.second.name.compare(top_region_) == 0) { + name = top_region_; } auto handle = DLB_MonitoringRegionRegister(name.c_str()); + int dlb_error = + DLB_TALP_CollectPOPMetrics(handle, ®ion.second.pop_metrics); - dlb_pop_metrics_t pop_metrics; - int error = DLB_TALP_CollectPOPMetrics(handle, &pop_metrics); - - if (error != DLB_SUCCESS) { - + if (dlb_error != DLB_SUCCESS) { + // Warn about the error trying to close region name with hash } - - std::string name_with_tree_lines = current_region.tree_lines + current_region.name; - - region_stream_stdout << std::setw(max_width+3) << std::left << name_with_tree_lines; - region_stream_stdout << std::fixed << std::setprecision(2); - region_stream_stdout << std::setw(15) << std::right << 100 * pop_metrics.elapsed_time/1000000000 << "s"; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.parallel_efficiency; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.mpi_parallel_efficiency; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.mpi_communication_efficiency; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.mpi_load_balance; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.mpi_load_balance_in; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.mpi_load_balance_out; - - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.omp_parallel_efficiency; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.omp_load_balance; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.omp_scheduling_efficiency; - region_stream_stdout << std::setw(8) << std::right << 100 * pop_metrics.omp_serialization_efficiency; - region_stream_stdout << std::endl; - - region_stream_csv << name_with_tree_lines; - region_stream_csv << std::fixed << std::setprecision(4); - region_stream_csv << "," << pop_metrics.elapsed_time/1000000000 << "s"; - region_stream_csv << "," << 100 * pop_metrics.parallel_efficiency; - region_stream_csv << "," << 100 * pop_metrics.mpi_parallel_efficiency; - region_stream_csv << "," << 100 * pop_metrics.mpi_communication_efficiency; - region_stream_csv << "," << 100 * pop_metrics.mpi_load_balance; - region_stream_csv << "," << 100 * pop_metrics.mpi_load_balance_in; - region_stream_csv << "," << 100 * pop_metrics.mpi_load_balance_out; - - region_stream_csv << "," << 100 * pop_metrics.omp_parallel_efficiency; - region_stream_csv << "," << 100 * pop_metrics.omp_load_balance; - region_stream_csv << "," << 100 * pop_metrics.omp_scheduling_efficiency; - region_stream_csv << "," << 100 * pop_metrics.omp_serialization_efficiency; - region_stream_csv << std::endl; } - if(mpi_helper_.IsRankNumber(0)){ - std::cout << region_stream_stdout.str() << std::endl; - } + std::size_t top = std::hash{}(top_region_); + bool enable_json_output = + env_enable_json_.getValue().value_or(def_enable_json_); + if (mpi_helper_.IsRankNumber(0) && enable_json_output) { + // Print the regions metrics in JSON format + DLBTalpTreeJSONSerializer json_printer(top, regions_); - if(mpi_helper_.IsRankNumber(0)){ - std::ofstream csv_stream; - csv_stream.open("nesmik_talp_tree.csv", std::ios::trunc); - csv_stream << region_stream_csv.str() << std::endl; - } + std::string json_name = + env_json_file_name_.getValue().value_or(def_json_file_name_); + std::ofstream json_stream; + json_stream.open(json_name, std::ios::trunc); + json_stream << json_printer.dump() << std::endl; + } + bool enable_ascii_output = + env_enable_ascii_.getValue().value_or(def_enable_ascii_); + if (mpi_helper_.IsRankNumber(0) && enable_ascii_output) { + // Print the regions metrics in ASCII format + DLBTalpTreeASCIISerializer ascii_serializer(top, regions_); + std::cout << ascii_serializer.dump() << std::endl; + } - this->talp_profiling_strategy.Finalize(); + // Stop TALP + talp_profiling_strategy_.Finalize(); } diff --git a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.hpp b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.hpp index 83e51f7ea8ee262b09bf0002347b7011d262543a..029ec8232a9d32ef4580ef420b97a51dced12e79 100644 --- a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.hpp +++ b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree.hpp @@ -2,40 +2,63 @@ #define SIT_DLB_TALP_TREE_H #include "../dlb/dlb.hpp" +#include "dlb.h" +#include "dlb_talp.h" #include "strategies.hpp" #include -#include -#include #include +#include +#include +#include #include struct TalpRegionNode { - std::string name; - std::string tree_lines; - std::size_t parent; - std::vector childs; + std::string name; + std::size_t parent; + std::vector childs; + dlb_pop_metrics_t pop_metrics; }; class DLBTalpTreeStrategy : public ProperlyNestedAnnotationStrategy { private: - DLBTalpStrategy talp_profiling_strategy; + DLBTalpStrategy talp_profiling_strategy_; - inline static const std::string top_region = "Application"; - std::size_t current_region; - std::map regions; + inline static const std::string top_region_ = "Application"; + std::size_t current_region_; + std::map regions_; // Mpi aware stuff MPIHelper mpi_helper_; + // Configuration variables + inline static const std::string def_json_file_name_ = "nesmik_talp_tree.json"; + EnvironmentVariable env_json_file_name_ = EnvironmentVariable< + std::string>( + "JSON_FILE", + "Set the name of the output JSON file. (default: nesmik_talp_tree.json)", + false); + + inline static const bool def_enable_json_ = true; + EnvironmentVariable env_enable_json_ = EnvironmentVariable( + "JSON_OUTPUT", "Enables output to a JSON file.", false); + + inline static const bool def_enable_ascii_ = false; + EnvironmentVariable env_enable_ascii_ = EnvironmentVariable( + "ASCII_OUTPUT", + "Enables human readable output to standard output. (default: off)", + false); + public: DLBTalpTreeStrategy(); - inline static const std::string_view name = "TALP-Tree"; - void RegionStart(const ProperlyNestedRegionInformation ®ion) noexcept override; - void RegionStopLast(const ProperlyNestedRegionInformation ®ion) noexcept override; - virtual void Init() noexcept; - virtual void Finalize() noexcept; + inline static const std::string_view name = "TALP::Tree"; + void + RegionStart(const ProperlyNestedRegionInformation ®ion) noexcept override; + void RegionStopLast( + const ProperlyNestedRegionInformation ®ion) noexcept override; + virtual void Init() noexcept; + virtual void Finalize() noexcept; }; #endif // DLB_TALP_TREE_H diff --git a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_ascii_serializer.cpp b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_ascii_serializer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7873f3fe98f3782ddc7214cacba532c87ba97b85 --- /dev/null +++ b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_ascii_serializer.cpp @@ -0,0 +1,117 @@ + +#include +#include +#include +#include + +#include "dlb_talp_tree_ascii_serializer.hpp" + +using json = nlohmann::json; + +DLBTalpTreeASCIISerializer::DLBTalpTreeASCIISerializer(std::size_t top_region, std::map const& regions) + : top_region(top_region) + , regions(regions) + , tree_lines{} + {} + +struct RegionStackEntry { + std::size_t hash; + std::size_t level; + std::string tree_lines; +}; + +std::size_t DLBTalpTreeASCIISerializer::compute_tree_lines() { + + std::stack regions_stack; + regions_stack.push({ + .hash = this->top_region, + .level = 0, + .tree_lines = "" + }); + + std::size_t max_width = 0; + + while (!regions_stack.empty()) { + + RegionStackEntry current_region = regions_stack.top(); + regions_stack.pop(); + + // I am the last child either if the stack is empty or if the level of the following element + // in the stack is lower than mine. + bool last_child = regions_stack.empty() || regions_stack.top().level < current_region.level; + + std::string next_tree_lines = " │ "; // By default I get the continuation line. + if (current_region.level == 0) // If I am the root region I don't need line. + next_tree_lines = ""; + else if (last_child) // If I am the last child I add space. + next_tree_lines = " "; + + for (auto region : this->regions.at(current_region.hash).childs) { + regions_stack.push({ + .hash = region, + .level = current_region.level + 1, + .tree_lines = current_region.tree_lines + next_tree_lines + }); + } + + std::size_t level = current_region.level; + std::string tree_lines = current_region.tree_lines; + if (level == 0) tree_lines += ""; // If I am the root I don't have lines + else if (last_child) tree_lines += " └─ "; // If I am the last child I close the line + else tree_lines += " ├─ "; // Otherwise i continue the line. + + this->tree_lines[current_region.hash] = tree_lines; + + max_width = std::max(max_width, + this->regions.at(current_region.hash).name.length() + tree_lines.length()); + } + +} + +std::string DLBTalpTreeASCIISerializer::generate_tree_view(int name_width) { + + std::stack regions_stack; + regions_stack.push(this->top_region); + + std::stringstream region_stream; + + while (!regions_stack.empty()) { + + std::size_t current_region_hash = regions_stack.top(); + regions_stack.pop(); + + for (std::size_t child_hash : this->regions.at(current_region_hash).childs) { + regions_stack.push(child_hash); + } + + TalpRegionNode current_region = this->regions.at(current_region_hash); + dlb_pop_metrics_t &pop_metrics = current_region.pop_metrics; + + std::string name_with_tree_lines = this->tree_lines[current_region_hash] + current_region.name; + + region_stream << std::setw(name_width+3) << std::left << name_with_tree_lines; + region_stream << std::fixed << std::setprecision(2); + region_stream << std::setw(15) << std::right << pop_metrics.elapsed_time/1000000000 << "s"; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.parallel_efficiency; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.mpi_parallel_efficiency; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.mpi_communication_efficiency; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.mpi_load_balance; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.mpi_load_balance_in; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.mpi_load_balance_out; + + region_stream << std::setw(8) << std::right << 100 * pop_metrics.omp_parallel_efficiency; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.omp_load_balance; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.omp_scheduling_efficiency; + region_stream << std::setw(8) << std::right << 100 * pop_metrics.omp_serialization_efficiency; + region_stream << std::endl; + + } + + return region_stream.str(); +} + +std::string DLBTalpTreeASCIISerializer::dump() { + std::size_t max_width = compute_tree_lines(); + std::string tree_view = generate_tree_view(max_width); + return tree_view; +} diff --git a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_ascii_serializer.hpp b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_ascii_serializer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..14e210aceed3b4a4a8d01aeac98c8fbe968c3ed8 --- /dev/null +++ b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_ascii_serializer.hpp @@ -0,0 +1,24 @@ +#ifndef NESMIK_DLB_TALP_TREE_ASCII_SERIALIZER_H +#define NESMIK_DLB_TALP_TREE_ASCII_SERIALIZER_H + +#include + +#include "dlb_talp_tree.hpp" + +class DLBTalpTreeASCIISerializer { + +private: + std::size_t top_region; + std::map const& regions; + std::map tree_lines; + +public: + DLBTalpTreeASCIISerializer(std::size_t top_region, const std::map ®ions); + std::string dump(); + +private: + std::size_t compute_tree_lines(); + std::string generate_tree_view(int name_width); +}; + +#endif // NESMIK_DLB_TALP_TREE_ASCII_SERIALIZER_H diff --git a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_json_serializer.cpp b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_json_serializer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fd44ae4161fd825fb9d0d2d879d6097e0e7d9160 --- /dev/null +++ b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_json_serializer.cpp @@ -0,0 +1,51 @@ + +#include +#include + + +#include "dlb_talp_tree_json_serializer.hpp" + +using json = nlohmann::json; + +DLBTalpTreeJSONSerializer::DLBTalpTreeJSONSerializer(std::size_t top_region, std::map const& regions) + : top_region(top_region) + , regions(regions) + {} + +void treeToJSON(const TalpRegionNode &node, json &talp_json, + std::map const& regions) { + + talp_json["regionName"] = node.name; + talp_json["metrics"] = { + {"elapsedTime", node.pop_metrics.elapsed_time}, + + {"parallelEfficiency", node.pop_metrics.parallel_efficiency}, + + {"mpiParallelEfficiency", node.pop_metrics.mpi_parallel_efficiency}, + {"mpiCommunicationEfficiency", node.pop_metrics.mpi_communication_efficiency}, + {"mpiLoadBalance", node.pop_metrics.mpi_load_balance}, + {"mpiLoadBalanceIn", node.pop_metrics.mpi_load_balance_in}, + {"mpiLoadBalanceOut", node.pop_metrics.mpi_load_balance_out}, + + {"ompParallelEfficiency", node.pop_metrics.omp_parallel_efficiency}, + {"ompLoadBalance", node.pop_metrics.omp_load_balance}, + {"ompSchedulingEfficiency", node.pop_metrics.omp_scheduling_efficiency}, + {"ompSerializationEfficiency", node.pop_metrics.omp_serialization_efficiency} + }; + + talp_json["subregions"] = json::array(); + for (std::size_t child : node.childs) { + json childJSON; + treeToJSON(regions.at(child), childJSON, regions); + talp_json["subregions"].push_back(childJSON); + } + +} + +std::string DLBTalpTreeJSONSerializer::dump() { + json talp_tree; + treeToJSON(this->regions.at(this->top_region), talp_tree, this->regions); + + // Activate pretty printing by giving an ammount of spaces bigger than 0. + return talp_tree.dump(/* ammount of spaces for indentation */ 2); +} diff --git a/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_json_serializer.hpp b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_json_serializer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4e279f4defd6757a6301f75c11ec621f11eb0e2c --- /dev/null +++ b/src/backends/dlb/dlb_talp_tree/dlb_talp_tree_json_serializer.hpp @@ -0,0 +1,19 @@ +#ifndef NESMIK_DLB_TALP_TREE_JSON_SERIALIZER_H +#define NESMIK_DLB_TALP_TREE_JSON_SERIALIZER_H + +#include + +#include "dlb_talp_tree.hpp" + +class DLBTalpTreeJSONSerializer { + +private: + std::size_t top_region; + std::map const& regions; + +public: + DLBTalpTreeJSONSerializer(std::size_t top_region, const std::map ®ions); + std::string dump(); +}; + +#endif // NESMIK_DLB_TALP_TREE_JSON_SERIALIZER_H diff --git a/src/utils/environment_variable.cpp b/src/utils/environment_variable.cpp index 5472e51d85841aad4b4a3cb05b20e2c7e38996b4..589d53d6351d03cd69c30ecdef18b45464864194 100644 --- a/src/utils/environment_variable.cpp +++ b/src/utils/environment_variable.cpp @@ -28,4 +28,8 @@ template <> std::optional fromEnvString(const std::string &env_string) { } else { return std::nullopt; } -} \ No newline at end of file +} + +template <> std::optional fromEnvString(const std::string &env_string) { + return std::optional{env_string}; +} diff --git a/src/utils/environment_variable.hpp b/src/utils/environment_variable.hpp index 2e87a7476887d921e7637d7e0e1ec326f3bfd027..d4543b0339aae48591c4bee7b50edfefa947536b 100644 --- a/src/utils/environment_variable.hpp +++ b/src/utils/environment_variable.hpp @@ -17,6 +17,7 @@ template std::optional fromEnvString(const std::string &env_string) = delete; template <> std::optional fromEnvString(const std::string &env_string); +template <> std::optional fromEnvString(const std::string &env_string); template class EnvironmentVariable { inline static std::string PREFIX = "NESMIK_"; @@ -52,4 +53,4 @@ public: bool isSet() const { return value_.has_value(); } }; -#endif // NESMIK_ENVIROMENT_VARIABLE_HPP \ No newline at end of file +#endif // NESMIK_ENVIROMENT_VARIABLE_HPP