#pragma once #include "util/Assert.hpp" #include "util/Concepts.hpp" #include "util/config/ConfigDefinition.hpp" #include "util/config/ConfigValue.hpp" #include "util/config/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::replaceInstance(nullptr); } 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 */ template struct WithPrometheusImpl : virtual ::testing::Test { WithPrometheusImpl() { config::ClioConfigDefinition const config{ {"prometheus.compress_reply", config::ConfigValue{config::ConfigType::Boolean}.defaultValue(false)}, {"prometheus.enabled", config::ConfigValue{config::ConfigType::Boolean}.defaultValue(IsEnabled)} }; PrometheusService::init(config); } ~WithPrometheusImpl() override { PrometheusService::replaceInstance(nullptr); } }; using WithPrometheus = WithPrometheusImpl<>; using WithPrometheusDisabled = WithPrometheusImpl; } // namespace util::prometheus