mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
Add compression and histogram metric type for Prometheus (#987)
Fixes #932 Also fixes #966 Decided not to add Summary type because it has the same functionality as Histogram but makes more calculations on client side (Clio side). See https://prometheus.io/docs/practices/histograms for detailed comparison.
This commit is contained in:
76
unittests/util/AtomicTests.cpp
Normal file
76
unittests/util/AtomicTests.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/Atomic.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace util;
|
||||
|
||||
TEST(AtomicTests, add)
|
||||
{
|
||||
Atomic<int> atomic{42};
|
||||
atomic.add(1);
|
||||
EXPECT_EQ(atomic.value(), 43);
|
||||
}
|
||||
|
||||
TEST(AtomicTests, set)
|
||||
{
|
||||
Atomic<int> atomic{42};
|
||||
atomic.set(1);
|
||||
EXPECT_EQ(atomic.value(), 1);
|
||||
}
|
||||
|
||||
TEST(AtomicTest, multithreadAddInt)
|
||||
{
|
||||
Atomic<int> atomic{0};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(100);
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
threads.emplace_back([&atomic] {
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
atomic.add(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
EXPECT_EQ(atomic.value(), 10000);
|
||||
}
|
||||
|
||||
TEST(AtomicTest, multithreadAddDouble)
|
||||
{
|
||||
Atomic<double> atomic{0.0};
|
||||
std::vector<std::thread> threads;
|
||||
threads.reserve(100);
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
threads.emplace_back([&atomic] {
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
atomic.add(1.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
EXPECT_NEAR(atomic.value(), 10000.0, 1e-9);
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
namespace util::prometheus {
|
||||
|
||||
template <detail::SomeNumberType NumberType>
|
||||
template <SomeNumberType NumberType>
|
||||
struct MockCounterImpl {
|
||||
using ValueType = NumberType;
|
||||
|
||||
@@ -39,8 +39,26 @@ using MockCounterImplInt = MockCounterImpl<std::int64_t>;
|
||||
using MockCounterImplUint = MockCounterImpl<std::uint64_t>;
|
||||
using MockCounterImplDouble = MockCounterImpl<double>;
|
||||
|
||||
template <typename NumberType>
|
||||
requires std::same_as<NumberType, std::int64_t> || std::same_as<NumberType, double>
|
||||
struct MockHistogramImpl {
|
||||
using ValueType = NumberType;
|
||||
|
||||
MockHistogramImpl()
|
||||
{
|
||||
EXPECT_CALL(*this, setBuckets);
|
||||
}
|
||||
|
||||
MOCK_METHOD(void, observe, (ValueType), ());
|
||||
MOCK_METHOD(void, setBuckets, (std::vector<ValueType> const&), ());
|
||||
MOCK_METHOD(void, serializeValue, (std::string const&, std::string, OStream&), (const));
|
||||
};
|
||||
|
||||
using MockHistogramImplInt = MockHistogramImpl<std::int64_t>;
|
||||
using MockHistogramImplDouble = MockHistogramImpl<double>;
|
||||
|
||||
struct MockPrometheusImpl : PrometheusInterface {
|
||||
MockPrometheusImpl() : PrometheusInterface(true)
|
||||
MockPrometheusImpl() : PrometheusInterface(true, true)
|
||||
{
|
||||
EXPECT_CALL(*this, counterInt)
|
||||
.WillRepeatedly([this](std::string name, Labels labels, std::optional<std::string>) -> CounterInt& {
|
||||
@@ -58,12 +76,34 @@ struct MockPrometheusImpl : PrometheusInterface {
|
||||
.WillRepeatedly([this](std::string name, Labels labels, std::optional<std::string>) -> GaugeDouble& {
|
||||
return getMetric<GaugeDouble>(std::move(name), std::move(labels));
|
||||
});
|
||||
EXPECT_CALL(*this, histogramInt)
|
||||
.WillRepeatedly(
|
||||
[this](std::string name, Labels labels, std::vector<std::int64_t> const&, std::optional<std::string>)
|
||||
-> HistogramInt& { return getMetric<HistogramInt>(std::move(name), std::move(labels)); }
|
||||
);
|
||||
EXPECT_CALL(*this, histogramDouble)
|
||||
.WillRepeatedly(
|
||||
[this](std::string name, Labels labels, std::vector<double> const&, std::optional<std::string>)
|
||||
-> HistogramDouble& { return getMetric<HistogramDouble>(std::move(name), std::move(labels)); }
|
||||
);
|
||||
}
|
||||
|
||||
MOCK_METHOD(CounterInt&, counterInt, (std::string, Labels, std::optional<std::string>), (override));
|
||||
MOCK_METHOD(CounterDouble&, counterDouble, (std::string, Labels, std::optional<std::string>), (override));
|
||||
MOCK_METHOD(GaugeInt&, gaugeInt, (std::string, Labels, std::optional<std::string>), (override));
|
||||
MOCK_METHOD(GaugeDouble&, gaugeDouble, (std::string, Labels, std::optional<std::string>), (override));
|
||||
MOCK_METHOD(
|
||||
HistogramInt&,
|
||||
histogramInt,
|
||||
(std::string, Labels, std::vector<std::int64_t> const&, std::optional<std::string>),
|
||||
(override)
|
||||
);
|
||||
MOCK_METHOD(
|
||||
HistogramDouble&,
|
||||
histogramDouble,
|
||||
(std::string, Labels, std::vector<double> const&, std::optional<std::string>),
|
||||
(override)
|
||||
);
|
||||
MOCK_METHOD(std::string, collectMetrics, (), (override));
|
||||
|
||||
template <typename MetricType>
|
||||
@@ -88,15 +128,23 @@ struct MockPrometheusImpl : PrometheusInterface {
|
||||
{
|
||||
std::unique_ptr<MetricBase> metric;
|
||||
auto const key = name + labelsString;
|
||||
if constexpr (std::is_same_v<typename MetricType::ValueType, std::int64_t>) {
|
||||
if constexpr (std::is_same_v<MetricType, GaugeInt>) {
|
||||
auto& impl = counterIntImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, impl);
|
||||
} else if constexpr (std::is_same_v<typename MetricType::ValueType, std::uint64_t>) {
|
||||
} else if constexpr (std::is_same_v<MetricType, CounterInt>) {
|
||||
auto& impl = counterUintImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, impl);
|
||||
} else {
|
||||
} else if constexpr (std::is_same_v<MetricType, GaugeDouble> || std::is_same_v<MetricType, CounterDouble>) {
|
||||
auto& impl = counterDoubleImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, impl);
|
||||
} else if constexpr (std::is_same_v<MetricType, HistogramInt>) {
|
||||
auto& impl = histogramIntImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, std::vector<std::int64_t>{1}, impl);
|
||||
} else if constexpr (std::is_same_v<MetricType, HistogramDouble>) {
|
||||
auto& impl = histogramDoubleImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, std::vector<double>{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<MetricType*>(ptr);
|
||||
@@ -108,6 +156,8 @@ struct MockPrometheusImpl : PrometheusInterface {
|
||||
std::unordered_map<std::string, ::testing::StrictMock<MockCounterImplInt>> counterIntImpls;
|
||||
std::unordered_map<std::string, ::testing::StrictMock<MockCounterImplUint>> counterUintImpls;
|
||||
std::unordered_map<std::string, ::testing::StrictMock<MockCounterImplDouble>> counterDoubleImpls;
|
||||
std::unordered_map<std::string, ::testing::StrictMock<MockHistogramImplInt>> histogramIntImpls;
|
||||
std::unordered_map<std::string, ::testing::StrictMock<MockHistogramImplDouble>> histogramDoubleImpls;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -147,15 +197,22 @@ struct WithMockPrometheus : virtual ::testing::Test {
|
||||
ASSERT(mockPrometheusPtr != nullptr, "Wrong prometheus type");
|
||||
|
||||
std::string const key = name + labelsString;
|
||||
mockPrometheusPtr->makeMetric<MetricType>(std::move(name), std::move(labelsString));
|
||||
if constexpr (std::is_same_v<typename MetricType::ValueType, std::int64_t>) {
|
||||
|
||||
if (!mockPrometheusPtr->metrics.contains(key))
|
||||
mockPrometheusPtr->makeMetric<MetricType>(std::move(name), std::move(labelsString));
|
||||
|
||||
if constexpr (std::is_same_v<MetricType, GaugeInt>) {
|
||||
return mockPrometheusPtr->counterIntImpls[key];
|
||||
} else if constexpr (std::is_same_v<typename MetricType::ValueType, std::uint64_t>) {
|
||||
} else if constexpr (std::is_same_v<MetricType, CounterInt>) {
|
||||
return mockPrometheusPtr->counterUintImpls[key];
|
||||
} else if constexpr (std::is_same_v<typename MetricType::ValueType, double>) {
|
||||
} else if constexpr (std::is_same_v<MetricType, GaugeDouble> || std::is_same_v<MetricType, CounterDouble>) {
|
||||
return mockPrometheusPtr->counterDoubleImpls[key];
|
||||
} else if constexpr (std::is_same_v<MetricType, HistogramInt>) {
|
||||
return mockPrometheusPtr->histogramIntImpls[key];
|
||||
} else if constexpr (std::is_same_v<MetricType, HistogramDouble>) {
|
||||
return mockPrometheusPtr->histogramDoubleImpls[key];
|
||||
}
|
||||
ASSERT(false, "Wrong metric type");
|
||||
ASSERT(false, "Wrong metric type for metric {} {}", name, labelsString);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,7 +222,8 @@ struct WithMockPrometheus : virtual ::testing::Test {
|
||||
struct WithPrometheus : virtual ::testing::Test {
|
||||
WithPrometheus()
|
||||
{
|
||||
PrometheusService::init();
|
||||
boost::json::value const config{{"prometheus", boost::json::object{{"compress_reply", false}}}};
|
||||
PrometheusService::init(Config{config});
|
||||
}
|
||||
|
||||
~WithPrometheus() override
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <util/prometheus/Counter.h>
|
||||
|
||||
#include <boost/iostreams/device/back_inserter.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -53,9 +54,9 @@ TEST_F(AnyCounterTests, labelsString)
|
||||
TEST_F(AnyCounterTests, serialize)
|
||||
{
|
||||
EXPECT_CALL(mockCounterImpl, value()).WillOnce(::testing::Return(42));
|
||||
std::string serialized;
|
||||
counter.serialize(serialized);
|
||||
EXPECT_EQ(serialized, R"(test_counter{label1="value1",label2="value2"} 42)");
|
||||
OStream stream{false};
|
||||
counter.serializeValue(stream);
|
||||
EXPECT_EQ(std::move(stream).data(), R"(test_counter{label1="value1",label2="value2"} 42)");
|
||||
}
|
||||
|
||||
TEST_F(AnyCounterTests, operatorAdd)
|
||||
|
||||
115
unittests/util/prometheus/HistogramTests.cpp
Normal file
115
unittests/util/prometheus/HistogramTests.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/prometheus/Histogram.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
struct AnyHistogramTests : ::testing::Test {
|
||||
struct MockHistogramImpl {
|
||||
MockHistogramImpl()
|
||||
{
|
||||
EXPECT_CALL(*this, setBuckets);
|
||||
}
|
||||
using ValueType = std::int64_t;
|
||||
MOCK_METHOD(void, observe, (ValueType));
|
||||
MOCK_METHOD(void, setBuckets, (std::vector<ValueType> const&));
|
||||
MOCK_METHOD(void, serializeValue, (std::string const&, std::string, OStream&), (const));
|
||||
};
|
||||
|
||||
::testing::StrictMock<MockHistogramImpl> mockHistogramImpl;
|
||||
std::string const name = "test_histogram";
|
||||
std::string labelsString = R"({label1="value1",label2="value2"})";
|
||||
HistogramInt histogram{name, labelsString, {1, 2, 3}, static_cast<MockHistogramImpl&>(mockHistogramImpl)};
|
||||
};
|
||||
|
||||
TEST_F(AnyHistogramTests, name)
|
||||
{
|
||||
EXPECT_EQ(histogram.name(), name);
|
||||
}
|
||||
|
||||
TEST_F(AnyHistogramTests, labelsString)
|
||||
{
|
||||
EXPECT_EQ(histogram.labelsString(), labelsString);
|
||||
}
|
||||
|
||||
TEST_F(AnyHistogramTests, observe)
|
||||
{
|
||||
EXPECT_CALL(mockHistogramImpl, observe(42));
|
||||
histogram.observe(42);
|
||||
}
|
||||
|
||||
TEST_F(AnyHistogramTests, serializeValue)
|
||||
{
|
||||
OStream stream{false};
|
||||
EXPECT_CALL(mockHistogramImpl, serializeValue(name, labelsString, ::testing::_));
|
||||
histogram.serializeValue(stream);
|
||||
}
|
||||
|
||||
struct HistogramTests : ::testing::Test {
|
||||
std::string labelsString = R"({label1="value1",label2="value2"})";
|
||||
HistogramInt histogram{"t", labelsString, {1, 2, 3}};
|
||||
|
||||
std::string
|
||||
serialize() const
|
||||
{
|
||||
OStream stream{false};
|
||||
histogram.serializeValue(stream);
|
||||
return std::move(stream).data();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(HistogramTests, observe)
|
||||
{
|
||||
histogram.observe(0);
|
||||
EXPECT_EQ(
|
||||
serialize(),
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"1\"} 1\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"2\"} 1\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"3\"} 1\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"+Inf\"} 1\n"
|
||||
"t_sum{label1=\"value1\",label2=\"value2\"} 0\n"
|
||||
"t_count{label1=\"value1\",label2=\"value2\"} 1\n"
|
||||
) << serialize();
|
||||
|
||||
histogram.observe(2);
|
||||
EXPECT_EQ(
|
||||
serialize(),
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"1\"} 1\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"2\"} 2\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"3\"} 2\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"+Inf\"} 2\n"
|
||||
"t_sum{label1=\"value1\",label2=\"value2\"} 2\n"
|
||||
"t_count{label1=\"value1\",label2=\"value2\"} 2\n"
|
||||
);
|
||||
|
||||
histogram.observe(123);
|
||||
EXPECT_EQ(
|
||||
serialize(),
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"1\"} 1\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"2\"} 2\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"3\"} 2\n"
|
||||
"t_bucket{label1=\"value1\",label2=\"value2\",le=\"+Inf\"} 3\n"
|
||||
"t_sum{label1=\"value1\",label2=\"value2\"} 125\n"
|
||||
"t_count{label1=\"value1\",label2=\"value2\"} 3\n"
|
||||
);
|
||||
}
|
||||
@@ -16,9 +16,11 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/prometheus/Http.h>
|
||||
|
||||
#include <util/MockPrometheus.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
@@ -47,7 +49,8 @@ struct PrometheusCheckRequestTests : public ::testing::TestWithParam<PrometheusC
|
||||
|
||||
TEST_P(PrometheusCheckRequestTests, isPrometheusRequest)
|
||||
{
|
||||
PrometheusService::init(util::Config{boost::json::value{{"prometheus_enabled", GetParam().prometheusEnabled}}});
|
||||
boost::json::value const configJson{{"prometheus", boost::json::object{{"enabled", GetParam().prometheusEnabled}}}};
|
||||
PrometheusService::init(util::Config{configJson});
|
||||
boost::beast::http::request<boost::beast::http::string_body> req;
|
||||
req.method(GetParam().method);
|
||||
req.target(GetParam().target);
|
||||
@@ -97,11 +100,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
PrometheusCheckRequestTests::NameGenerator()
|
||||
);
|
||||
|
||||
struct PrometheusHandleRequestTests : ::testing::Test {
|
||||
PrometheusHandleRequestTests()
|
||||
{
|
||||
PrometheusService::init();
|
||||
}
|
||||
struct PrometheusHandleRequestTests : util::prometheus::WithPrometheus {
|
||||
http::request<http::string_body> const req{http::verb::get, "/metrics", 11};
|
||||
};
|
||||
|
||||
@@ -116,7 +115,8 @@ TEST_F(PrometheusHandleRequestTests, emptyResponse)
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, prometheusDisabled)
|
||||
{
|
||||
PrometheusService::init(util::Config(boost::json::value{{"prometheus_enabled", false}}));
|
||||
boost::json::value const configJson({{"prometheus", boost::json::object{{"enabled", false}}}});
|
||||
PrometheusService::init(util::Config(configJson));
|
||||
auto response = handlePrometheusRequest(req, true);
|
||||
ASSERT_TRUE(response.has_value());
|
||||
EXPECT_EQ(response->result(), http::status::forbidden);
|
||||
@@ -151,7 +151,7 @@ TEST_F(PrometheusHandleRequestTests, responseWithCounter)
|
||||
TEST_F(PrometheusHandleRequestTests, responseWithGauge)
|
||||
{
|
||||
auto const gaugeName = "test_gauge";
|
||||
const Labels labels{{{"label2", "value2"}, Label{"label3", "value3"}}};
|
||||
const Labels labels{{{"label2", "value2"}, {"label3", "value3"}}};
|
||||
auto const description = "test_description_gauge";
|
||||
|
||||
auto& gauge = PrometheusService::gaugeInt(gaugeName, labels, description);
|
||||
@@ -170,7 +170,7 @@ TEST_F(PrometheusHandleRequestTests, responseWithGauge)
|
||||
TEST_F(PrometheusHandleRequestTests, responseWithCounterAndGauge)
|
||||
{
|
||||
auto const counterName = "test_counter";
|
||||
const Labels counterLabels{{{"label1", "value1"}, Label{"label2", "value2"}}};
|
||||
const Labels counterLabels{{{"label1", "value1"}, {"label2", "value2"}}};
|
||||
auto const counterDescription = "test_description";
|
||||
|
||||
auto& counter = PrometheusService::counterInt(counterName, counterLabels, counterDescription);
|
||||
@@ -178,7 +178,7 @@ TEST_F(PrometheusHandleRequestTests, responseWithCounterAndGauge)
|
||||
counter += 3;
|
||||
|
||||
auto const gaugeName = "test_gauge";
|
||||
const Labels gaugeLabels{{{"label2", "value2"}, Label{"label3", "value3"}}};
|
||||
const Labels gaugeLabels{{{"label2", "value2"}, {"label3", "value3"}}};
|
||||
auto const gaugeDescription = "test_description_gauge";
|
||||
|
||||
auto& gauge = PrometheusService::gaugeInt(gaugeName, gaugeLabels, gaugeDescription);
|
||||
@@ -211,3 +211,19 @@ TEST_F(PrometheusHandleRequestTests, responseWithCounterAndGauge)
|
||||
);
|
||||
EXPECT_TRUE(response->body() == expectedBody || response->body() == anotherExpectedBody);
|
||||
}
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, compressReply)
|
||||
{
|
||||
PrometheusService::init(util::Config(boost::json::value{
|
||||
{"prometheus", boost::json::object{{"compress_reply", true}}}}));
|
||||
|
||||
auto& gauge = PrometheusService::gaugeInt("test_gauge", Labels{});
|
||||
++gauge;
|
||||
|
||||
auto response = handlePrometheusRequest(req, true);
|
||||
ASSERT_TRUE(response.has_value());
|
||||
EXPECT_EQ(response->result(), http::status::ok);
|
||||
EXPECT_EQ(response->operator[](http::field::content_type), "text/plain; version=0.0.4");
|
||||
EXPECT_EQ(response->operator[](http::field::content_encoding), "gzip");
|
||||
EXPECT_GT(response->body().size(), 0ul);
|
||||
}
|
||||
|
||||
78
unittests/util/prometheus/MetricBuilderTests.cpp
Normal file
78
unittests/util/prometheus/MetricBuilderTests.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/prometheus/Counter.h>
|
||||
#include <util/prometheus/Gauge.h>
|
||||
#include <util/prometheus/Histogram.h>
|
||||
#include <util/prometheus/MetricBuilder.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
TEST(MetricBuilderTest, build)
|
||||
{
|
||||
std::string const name = "name";
|
||||
std::string const labelsString = "{label1=\"value1\"}";
|
||||
MetricBuilder builder;
|
||||
for (auto const type :
|
||||
{MetricType::COUNTER_INT,
|
||||
MetricType::COUNTER_DOUBLE,
|
||||
MetricType::GAUGE_INT,
|
||||
MetricType::GAUGE_DOUBLE,
|
||||
MetricType::HISTOGRAM_INT,
|
||||
MetricType::HISTOGRAM_DOUBLE}) {
|
||||
std::unique_ptr<MetricBase> metric = [&]() {
|
||||
if (type == MetricType::HISTOGRAM_INT)
|
||||
return builder(name, labelsString, type, std::vector<std::int64_t>{1});
|
||||
|
||||
if (type == MetricType::HISTOGRAM_DOUBLE)
|
||||
return builder(name, labelsString, type, std::vector<double>{1.});
|
||||
|
||||
return builder(name, labelsString, type);
|
||||
}();
|
||||
switch (type) {
|
||||
case MetricType::COUNTER_INT:
|
||||
EXPECT_NE(dynamic_cast<CounterInt*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::COUNTER_DOUBLE:
|
||||
EXPECT_NE(dynamic_cast<CounterDouble*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::GAUGE_INT:
|
||||
EXPECT_NE(dynamic_cast<GaugeInt*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::GAUGE_DOUBLE:
|
||||
EXPECT_NE(dynamic_cast<GaugeDouble*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::HISTOGRAM_INT:
|
||||
EXPECT_NE(dynamic_cast<HistogramInt*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::HISTOGRAM_DOUBLE:
|
||||
EXPECT_NE(dynamic_cast<HistogramDouble*>(metric.get()), nullptr);
|
||||
break;
|
||||
default:
|
||||
EXPECT_EQ(metric, nullptr);
|
||||
}
|
||||
if (metric != nullptr) {
|
||||
EXPECT_EQ(metric->name(), name);
|
||||
EXPECT_EQ(metric->labelsString(), labelsString);
|
||||
}
|
||||
}
|
||||
EXPECT_DEATH({ builder(name, labelsString, MetricType::SUMMARY, std::vector<std::int64_t>{}); }, "");
|
||||
}
|
||||
@@ -17,66 +17,62 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/prometheus/Counter.h>
|
||||
#include <util/prometheus/Gauge.h>
|
||||
#include <util/prometheus/Metrics.h>
|
||||
#include <util/prometheus/MetricsFamily.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
TEST(DefaultMetricBuilderTest, build)
|
||||
{
|
||||
std::string const name = "name";
|
||||
std::string const labelsString = "{label1=\"value1\"}";
|
||||
for (auto const type :
|
||||
{MetricType::COUNTER_INT, MetricType::COUNTER_DOUBLE, MetricType::GAUGE_INT, MetricType::GAUGE_DOUBLE}) {
|
||||
auto metric = MetricsFamily::defaultMetricBuilder(name, labelsString, type);
|
||||
switch (type) {
|
||||
case MetricType::COUNTER_INT:
|
||||
EXPECT_NE(dynamic_cast<CounterInt*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::COUNTER_DOUBLE:
|
||||
EXPECT_NE(dynamic_cast<CounterDouble*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::GAUGE_INT:
|
||||
EXPECT_NE(dynamic_cast<GaugeInt*>(metric.get()), nullptr);
|
||||
break;
|
||||
case MetricType::GAUGE_DOUBLE:
|
||||
EXPECT_NE(dynamic_cast<GaugeDouble*>(metric.get()), nullptr);
|
||||
break;
|
||||
default:
|
||||
EXPECT_EQ(metric, nullptr);
|
||||
}
|
||||
if (metric != nullptr) {
|
||||
EXPECT_EQ(metric->name(), name);
|
||||
EXPECT_EQ(metric->labelsString(), labelsString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MetricsFamilyTest : ::testing::Test {
|
||||
struct MetricMock : MetricBase {
|
||||
using MetricBase::MetricBase;
|
||||
MOCK_METHOD(void, serializeValue, (std::string&), (const));
|
||||
MOCK_METHOD(void, serializeValue, (OStream&), (const));
|
||||
};
|
||||
using MetricStrictMock = ::testing::StrictMock<MetricMock>;
|
||||
|
||||
struct MetricBuilderImplMock {
|
||||
MOCK_METHOD(std::unique_ptr<MetricBase>, build, (std::string, std::string, MetricType));
|
||||
struct MetricBuilderImplMock : MetricBuilderInterface {
|
||||
std::unique_ptr<MetricBase>
|
||||
operator()(
|
||||
std::string metricName,
|
||||
std::string labelsString,
|
||||
MetricType metricType,
|
||||
std::vector<std::int64_t> const& buckets
|
||||
) override
|
||||
{
|
||||
return buildInt(std::move(metricName), std::move(labelsString), metricType, buckets);
|
||||
}
|
||||
|
||||
std::unique_ptr<MetricBase>
|
||||
operator()(
|
||||
std::string metricName,
|
||||
std::string labelsString,
|
||||
MetricType metricType,
|
||||
std::vector<double> const& buckets
|
||||
) override
|
||||
{
|
||||
return buildDouble(std::move(metricName), std::move(labelsString), metricType, buckets);
|
||||
}
|
||||
|
||||
MOCK_METHOD(
|
||||
std::unique_ptr<MetricBase>,
|
||||
buildInt,
|
||||
(std::string, std::string, MetricType, std::vector<std::int64_t> const&)
|
||||
);
|
||||
MOCK_METHOD(
|
||||
std::unique_ptr<MetricBase>,
|
||||
buildDouble,
|
||||
(std::string, std::string, MetricType, std::vector<double> const&)
|
||||
);
|
||||
};
|
||||
|
||||
::testing::StrictMock<MetricBuilderImplMock> metricBuilderMock;
|
||||
MetricsFamily::MetricBuilder metricBuilder =
|
||||
[this](std::string metricName, std::string labels, MetricType metricType) {
|
||||
return metricBuilderMock.build(std::move(metricName), std::move(labels), metricType);
|
||||
};
|
||||
|
||||
std::string const name{"name"};
|
||||
std::string const description{"description"};
|
||||
MetricType const type{MetricType::COUNTER_INT};
|
||||
MetricsFamily metricsFamily{name, description, type, metricBuilder};
|
||||
MetricsFamily metricsFamily{name, description, type, metricBuilderMock};
|
||||
};
|
||||
|
||||
TEST_F(MetricsFamilyTest, getters)
|
||||
@@ -90,7 +86,7 @@ TEST_F(MetricsFamilyTest, getMetric)
|
||||
Labels const labels{{{"label1", "value1"}}};
|
||||
std::string const labelsString = labels.serialize();
|
||||
|
||||
EXPECT_CALL(metricBuilderMock, build(name, labelsString, type))
|
||||
EXPECT_CALL(metricBuilderMock, buildInt(name, labelsString, type, std::vector<std::int64_t>{}))
|
||||
.WillOnce(::testing::Return(std::make_unique<MetricStrictMock>(name, labelsString)));
|
||||
|
||||
auto& metric = metricsFamily.getMetric(labels);
|
||||
@@ -104,7 +100,7 @@ TEST_F(MetricsFamilyTest, getMetric)
|
||||
Labels const labels2{{{"label1", "value2"}}};
|
||||
std::string const labels2String = labels2.serialize();
|
||||
|
||||
EXPECT_CALL(metricBuilderMock, build(name, labels2String, type))
|
||||
EXPECT_CALL(metricBuilderMock, buildInt(name, labels2String, type, std::vector<std::int64_t>{}))
|
||||
.WillOnce(::testing::Return(std::make_unique<MetricStrictMock>(name, labels2String)));
|
||||
|
||||
auto& metric2 = metricsFamily.getMetric(labels2);
|
||||
@@ -116,11 +112,12 @@ TEST_F(MetricsFamilyTest, getMetric)
|
||||
EXPECT_EQ(&metricsFamily.getMetric(labels2), &metric2);
|
||||
EXPECT_NE(&metric, &metric2);
|
||||
|
||||
EXPECT_CALL(*metricMock, serializeValue(::testing::_)).WillOnce([](std::string& s) { s += "metric"; });
|
||||
EXPECT_CALL(*metric2Mock, serializeValue(::testing::_)).WillOnce([](std::string& s) { s += "metric2"; });
|
||||
EXPECT_CALL(*metricMock, serializeValue(::testing::_)).WillOnce([](OStream& s) { s << "metric"; });
|
||||
EXPECT_CALL(*metric2Mock, serializeValue(::testing::_)).WillOnce([](OStream& s) { s << "metric2"; });
|
||||
|
||||
std::string serialized;
|
||||
metricsFamily.serialize(serialized);
|
||||
OStream stream{false};
|
||||
stream << metricsFamily;
|
||||
auto const serialized = std::move(stream).data();
|
||||
|
||||
auto const expected =
|
||||
fmt::format("# HELP {0} {1}\n# TYPE {0} {2}\nmetric\nmetric2\n\n", name, description, toString(type));
|
||||
57
unittests/util/prometheus/OStreamTests.cpp
Normal file
57
unittests/util/prometheus/OStreamTests.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <util/prometheus/OStream.h>
|
||||
|
||||
#include <boost/iostreams/filter/gzip.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
TEST(OStreamTests, empty)
|
||||
{
|
||||
OStream stream{false};
|
||||
EXPECT_EQ(std::move(stream).data(), "");
|
||||
}
|
||||
|
||||
TEST(OStreamTests, string)
|
||||
{
|
||||
OStream stream{false};
|
||||
stream << "hello";
|
||||
EXPECT_EQ(std::move(stream).data(), "hello");
|
||||
}
|
||||
|
||||
TEST(OStreamTests, compression)
|
||||
{
|
||||
OStream stream{true};
|
||||
std::string const str = "helloooooooooooooooooooooooooooooooooo";
|
||||
stream << str;
|
||||
auto const compressed = std::move(stream).data();
|
||||
EXPECT_LT(compressed.size(), str.size());
|
||||
|
||||
std::string const decompressed = [&compressed]() {
|
||||
std::string result;
|
||||
boost::iostreams::filtering_istream stream;
|
||||
stream.push(boost::iostreams::gzip_decompressor{});
|
||||
stream.push(boost::iostreams::array_source{compressed.data(), compressed.size()});
|
||||
stream >> result;
|
||||
return result;
|
||||
}();
|
||||
EXPECT_EQ(decompressed, str);
|
||||
}
|
||||
Reference in New Issue
Block a user