//------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio Copyright (c) 2023, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #pragma once #include "util/Assert.hpp" #include "util/Concepts.hpp" #include "util/newconfig/ConfigDefinition.hpp" #include "util/newconfig/ConfigValue.hpp" #include "util/newconfig/Types.hpp" #include "util/prometheus/Bool.hpp" #include "util/prometheus/Counter.hpp" #include "util/prometheus/Gauge.hpp" #include "util/prometheus/Histogram.hpp" #include "util/prometheus/Label.hpp" #include "util/prometheus/MetricBase.hpp" #include "util/prometheus/OStream.hpp" #include "util/prometheus/Prometheus.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace util::prometheus { template struct MockCounterImpl { using ValueType = NumberType; MOCK_METHOD(void, add, (NumberType), ()); MOCK_METHOD(void, set, (NumberType), ()); MOCK_METHOD(NumberType, value, (), ()); }; using MockCounterImplInt = MockCounterImpl; using MockCounterImplUint = MockCounterImpl; using MockCounterImplDouble = MockCounterImpl; template requires std::same_as || std::same_as struct MockHistogramImpl { using ValueType = NumberType; MockHistogramImpl() { EXPECT_CALL(*this, setBuckets); } MOCK_METHOD(void, observe, (ValueType), ()); MOCK_METHOD(void, setBuckets, (std::vector const&), ()); MOCK_METHOD(void, serializeValue, (std::string const&, std::string, OStream&), (const)); }; using MockHistogramImplInt = MockHistogramImpl; using MockHistogramImplDouble = MockHistogramImpl; struct MockPrometheusImpl : PrometheusInterface { MockPrometheusImpl() : PrometheusInterface(true, true) { EXPECT_CALL(*this, boolMetric) .WillRepeatedly([this](std::string name, Labels labels, std::optional) -> Bool { return Bool{getMetric(std::move(name), std::move(labels))}; }); EXPECT_CALL(*this, counterInt) .WillRepeatedly([this](std::string name, Labels labels, std::optional) -> CounterInt& { return getMetric(std::move(name), std::move(labels)); }); EXPECT_CALL(*this, counterDouble) .WillRepeatedly([this](std::string name, Labels labels, std::optional) -> CounterDouble& { return getMetric(std::move(name), std::move(labels)); }); EXPECT_CALL(*this, gaugeInt) .WillRepeatedly([this](std::string name, Labels labels, std::optional) -> GaugeInt& { return getMetric(std::move(name), std::move(labels)); }); EXPECT_CALL(*this, gaugeDouble) .WillRepeatedly([this](std::string name, Labels labels, std::optional) -> GaugeDouble& { return getMetric(std::move(name), std::move(labels)); }); EXPECT_CALL(*this, histogramInt) .WillRepeatedly( [this](std::string name, Labels labels, std::vector const&, std::optional) -> HistogramInt& { return getMetric(std::move(name), std::move(labels)); } ); EXPECT_CALL(*this, histogramDouble) .WillRepeatedly( [this](std::string name, Labels labels, std::vector const&, std::optional) -> HistogramDouble& { return getMetric(std::move(name), std::move(labels)); } ); } MOCK_METHOD(Bool, boolMetric, (std::string, Labels, std::optional), (override)); MOCK_METHOD(CounterInt&, counterInt, (std::string, Labels, std::optional), (override)); MOCK_METHOD(CounterDouble&, counterDouble, (std::string, Labels, std::optional), (override)); MOCK_METHOD(GaugeInt&, gaugeInt, (std::string, Labels, std::optional), (override)); MOCK_METHOD(GaugeDouble&, gaugeDouble, (std::string, Labels, std::optional), (override)); MOCK_METHOD( HistogramInt&, histogramInt, (std::string, Labels, std::vector const&, std::optional), (override) ); MOCK_METHOD( HistogramDouble&, histogramDouble, (std::string, Labels, std::vector const&, std::optional), (override) ); MOCK_METHOD(std::string, collectMetrics, (), (override)); template MetricType& getMetric(std::string name, Labels labels) { auto labelsString = labels.serialize(); auto const key = name + labelsString; auto it = metrics.find(key); if (it == metrics.end()) { return makeMetric(std::move(name), std::move(labelsString)); } auto* basePtr = it->second.get(); auto* metricPtr = dynamic_cast(basePtr); ASSERT(metricPtr != nullptr, "Wrong metric type"); return *metricPtr; } template MetricType& makeMetric(std::string name, std::string labelsString) { std::unique_ptr metric; auto const key = name + labelsString; if constexpr (std::is_same_v or std::is_same_v) { auto& impl = counterIntImpls[key]; metric = std::make_unique(name, labelsString, impl); } else if constexpr (std::is_same_v) { auto& impl = counterUintImpls[key]; metric = std::make_unique(name, labelsString, impl); } else if constexpr (std::is_same_v || std::is_same_v) { auto& impl = counterDoubleImpls[key]; metric = std::make_unique(name, labelsString, impl); } else if constexpr (std::is_same_v) { auto& impl = histogramIntImpls[key]; metric = std::make_unique(name, labelsString, std::vector{1}, impl); } else if constexpr (std::is_same_v) { auto& impl = histogramDoubleImpls[key]; metric = std::make_unique(name, labelsString, std::vector{1.}, impl); } else { throw std::runtime_error("Wrong metric type"); } auto* ptr = metrics.emplace(key, std::move(metric)).first->second.get(); auto metricPtr = dynamic_cast(ptr); ASSERT(metricPtr != nullptr, "Wrong metric type"); return *metricPtr; } std::unordered_map> metrics; std::unordered_map> counterIntImpls; std::unordered_map> counterUintImpls; std::unordered_map> counterDoubleImpls; std::unordered_map> histogramIntImpls; std::unordered_map> histogramDoubleImpls; }; /** * @note this class should be the first in the inheritance list */ struct WithMockPrometheus : virtual ::testing::Test { WithMockPrometheus() { PrometheusService::replaceInstance(std::make_unique()); } ~WithMockPrometheus() override { if (HasFailure()) { std::cerr << "Registered metrics:\n"; for (auto const& [key, metric] : mockPrometheus().metrics) { std::cerr << key << "\n"; } std::cerr << "\n"; } config::ClioConfigDefinition const config{ {"prometheus.compress_reply", config::ConfigValue{config::ConfigType::Boolean}.defaultValue(true)}, {"prometheus.enabled", config::ConfigValue{config::ConfigType::Boolean}.defaultValue(true)} }; PrometheusService::init(config); } static MockPrometheusImpl& mockPrometheus() { auto* ptr = dynamic_cast(&PrometheusService::instance()); ASSERT(ptr != nullptr, "Wrong prometheus type"); return *ptr; } template static auto& makeMock(std::string name, std::string labelsString) { auto* mockPrometheusPtr = dynamic_cast(&PrometheusService::instance()); ASSERT(mockPrometheusPtr != nullptr, "Wrong prometheus type"); std::string const key = name + labelsString; if (!mockPrometheusPtr->metrics.contains(key)) mockPrometheusPtr->makeMetric(std::move(name), std::move(labelsString)); if constexpr (std::is_same_v or std::is_same_v) { return mockPrometheusPtr->counterIntImpls[key]; } else if constexpr (std::is_same_v) { return mockPrometheusPtr->counterUintImpls[key]; } else if constexpr (std::is_same_v || std::is_same_v) { return mockPrometheusPtr->counterDoubleImpls[key]; } else if constexpr (std::is_same_v) { return mockPrometheusPtr->histogramIntImpls[key]; } else if constexpr (std::is_same_v) { return mockPrometheusPtr->histogramDoubleImpls[key]; } ASSERT(false, "Wrong metric type for metric {} {}", name, labelsString); // to fix -Werror=return-type for gcc 14.1 in Debug mode throw std::runtime_error("Wrong metric type"); } }; /** * @note this class should be the first in the inheritance list */ struct WithPrometheus : virtual ::testing::Test { WithPrometheus() { config::ClioConfigDefinition const config{ {"prometheus.compress_reply", config::ConfigValue{config::ConfigType::Boolean}.defaultValue(false)}, {"prometheus.enabled", config::ConfigValue{config::ConfigType::Boolean}.defaultValue(true)} }; PrometheusService::init(config); } ~WithPrometheus() override { PrometheusService::replaceInstance(nullptr); } }; } // namespace util::prometheus