mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
181
unittests/util/MockPrometheus.h
Normal file
181
unittests/util/MockPrometheus.h
Normal file
@@ -0,0 +1,181 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/prometheus/Prometheus.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace util::prometheus {
|
||||
|
||||
template <detail::SomeNumberType NumberType>
|
||||
struct MockCounterImpl {
|
||||
using ValueType = NumberType;
|
||||
|
||||
MOCK_METHOD(void, add, (NumberType), ());
|
||||
MOCK_METHOD(void, set, (NumberType), ());
|
||||
MOCK_METHOD(NumberType, value, (), ());
|
||||
};
|
||||
|
||||
using MockCounterImplInt = MockCounterImpl<std::int64_t>;
|
||||
using MockCounterImplUint = MockCounterImpl<std::uint64_t>;
|
||||
using MockCounterImplDouble = MockCounterImpl<double>;
|
||||
|
||||
struct MockPrometheusImpl : PrometheusInterface {
|
||||
MockPrometheusImpl() : PrometheusInterface(true)
|
||||
{
|
||||
EXPECT_CALL(*this, counterInt)
|
||||
.WillRepeatedly([this](std::string name, Labels labels, std::optional<std::string>) -> CounterInt& {
|
||||
return getMetric<CounterInt>(std::move(name), std::move(labels));
|
||||
});
|
||||
EXPECT_CALL(*this, counterDouble)
|
||||
.WillRepeatedly([this](std::string name, Labels labels, std::optional<std::string>) -> CounterDouble& {
|
||||
return getMetric<CounterDouble>(std::move(name), std::move(labels));
|
||||
});
|
||||
EXPECT_CALL(*this, gaugeInt)
|
||||
.WillRepeatedly([this](std::string name, Labels labels, std::optional<std::string>) -> GaugeInt& {
|
||||
return getMetric<GaugeInt>(std::move(name), std::move(labels));
|
||||
});
|
||||
EXPECT_CALL(*this, gaugeDouble)
|
||||
.WillRepeatedly([this](std::string name, Labels labels, std::optional<std::string>) -> GaugeDouble& {
|
||||
return getMetric<GaugeDouble>(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(std::string, collectMetrics, (), (override));
|
||||
|
||||
template <typename MetricType>
|
||||
MetricType&
|
||||
getMetric(std::string name, Labels labels)
|
||||
{
|
||||
auto const labelsString = labels.serialize();
|
||||
auto const key = name + labels.serialize();
|
||||
auto it = metrics.find(key);
|
||||
if (it == metrics.end()) {
|
||||
return makeMetric<MetricType>(std::move(name), labels.serialize());
|
||||
}
|
||||
auto* basePtr = it->second.get();
|
||||
auto* metricPtr = dynamic_cast<MetricType*>(basePtr);
|
||||
if (metricPtr == nullptr)
|
||||
throw std::runtime_error("Wrong metric type");
|
||||
return *metricPtr;
|
||||
}
|
||||
|
||||
template <typename MetricType>
|
||||
MetricType&
|
||||
makeMetric(std::string name, std::string labelsString)
|
||||
{
|
||||
std::unique_ptr<MetricBase> metric;
|
||||
auto const key = name + labelsString;
|
||||
if constexpr (std::is_same_v<typename MetricType::ValueType, std::int64_t>) {
|
||||
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>) {
|
||||
auto& impl = counterUintImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, impl);
|
||||
} else {
|
||||
auto& impl = counterDoubleImpls[key];
|
||||
metric = std::make_unique<MetricType>(name, labelsString, impl);
|
||||
}
|
||||
auto* ptr = metrics.emplace(key, std::move(metric)).first->second.get();
|
||||
auto metricPtr = dynamic_cast<MetricType*>(ptr);
|
||||
if (metricPtr == nullptr)
|
||||
throw std::runtime_error("Wrong metric type");
|
||||
return *metricPtr;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::unique_ptr<MetricBase>> metrics;
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* @note this class should be the first in the inheritance list
|
||||
*/
|
||||
struct WithMockPrometheus : virtual ::testing::Test {
|
||||
WithMockPrometheus()
|
||||
{
|
||||
PrometheusService::replaceInstance(std::make_unique<MockPrometheusImpl>());
|
||||
}
|
||||
|
||||
~WithMockPrometheus() override
|
||||
{
|
||||
if (HasFailure()) {
|
||||
std::cerr << "Registered metrics:\n";
|
||||
for (auto const& [key, metric] : mockPrometheus().metrics) {
|
||||
std::cerr << key << "\n";
|
||||
}
|
||||
std::cerr << "\n";
|
||||
}
|
||||
PrometheusService::init();
|
||||
}
|
||||
|
||||
static MockPrometheusImpl&
|
||||
mockPrometheus()
|
||||
{
|
||||
auto* ptr = dynamic_cast<MockPrometheusImpl*>(&PrometheusService::instance());
|
||||
if (ptr == nullptr)
|
||||
throw std::runtime_error("Wrong prometheus type");
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
template <typename MetricType>
|
||||
static auto&
|
||||
makeMock(std::string name, std::string labelsString)
|
||||
{
|
||||
auto* mockPrometheusPtr = dynamic_cast<MockPrometheusImpl*>(&PrometheusService::instance());
|
||||
if (mockPrometheusPtr == nullptr)
|
||||
throw std::runtime_error("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>) {
|
||||
return mockPrometheusPtr->counterIntImpls[key];
|
||||
} else if constexpr (std::is_same_v<typename MetricType::ValueType, std::uint64_t>) {
|
||||
return mockPrometheusPtr->counterUintImpls[key];
|
||||
} else if constexpr (std::is_same_v<typename MetricType::ValueType, double>) {
|
||||
return mockPrometheusPtr->counterDoubleImpls[key];
|
||||
}
|
||||
throw std::runtime_error("Wrong metric type");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @note this class should be the first in the inheritance list
|
||||
*/
|
||||
struct WithPrometheus : virtual ::testing::Test {
|
||||
WithPrometheus()
|
||||
{
|
||||
PrometheusService::init();
|
||||
}
|
||||
|
||||
~WithPrometheus() override
|
||||
{
|
||||
PrometheusService::init();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace util::prometheus
|
||||
@@ -47,6 +47,32 @@ struct HttpSyncClient {
|
||||
std::string const& body,
|
||||
std::vector<WebHeader> additionalHeaders = {}
|
||||
)
|
||||
{
|
||||
return syncRequest(host, port, body, std::move(additionalHeaders), http::verb::post);
|
||||
}
|
||||
|
||||
static std::string
|
||||
syncGet(
|
||||
std::string const& host,
|
||||
std::string const& port,
|
||||
std::string const& body,
|
||||
std::string const& target,
|
||||
std::vector<WebHeader> additionalHeaders = {}
|
||||
)
|
||||
{
|
||||
return syncRequest(host, port, body, std::move(additionalHeaders), http::verb::get, target);
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string
|
||||
syncRequest(
|
||||
std::string const& host,
|
||||
std::string const& port,
|
||||
std::string const& body,
|
||||
std::vector<WebHeader> additionalHeaders,
|
||||
http::verb method,
|
||||
std::string target = "/"
|
||||
)
|
||||
{
|
||||
boost::asio::io_context ioc;
|
||||
|
||||
@@ -56,14 +82,15 @@ struct HttpSyncClient {
|
||||
auto const results = resolver.resolve(host, port);
|
||||
stream.connect(results);
|
||||
|
||||
http::request<http::string_body> req{http::verb::post, "/", 10};
|
||||
http::request<http::string_body> req{method, "/", 10};
|
||||
req.set(http::field::host, host);
|
||||
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
|
||||
|
||||
for (auto& header : additionalHeaders) {
|
||||
req.set(header.name, std::move(header.value));
|
||||
req.set(header.name, header.value);
|
||||
}
|
||||
|
||||
req.target(target);
|
||||
req.body() = std::string(body);
|
||||
req.prepare_payload();
|
||||
http::write(stream, req);
|
||||
|
||||
157
unittests/util/prometheus/CounterTests.cpp
Normal file
157
unittests/util/prometheus/CounterTests.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
struct AnyCounterTests : ::testing::Test {
|
||||
struct MockCounterImpl {
|
||||
using ValueType = std::uint64_t;
|
||||
MOCK_METHOD(void, add, (ValueType));
|
||||
MOCK_METHOD(void, set, (ValueType));
|
||||
MOCK_METHOD(ValueType, value, (), (const));
|
||||
};
|
||||
|
||||
::testing::StrictMock<MockCounterImpl> mockCounterImpl;
|
||||
std::string const name = "test_counter";
|
||||
std::string labelsString = R"({label1="value1",label2="value2"})";
|
||||
CounterInt counter{name, labelsString, static_cast<MockCounterImpl&>(mockCounterImpl)};
|
||||
};
|
||||
|
||||
TEST_F(AnyCounterTests, name)
|
||||
{
|
||||
EXPECT_EQ(counter.name(), name);
|
||||
}
|
||||
|
||||
TEST_F(AnyCounterTests, labelsString)
|
||||
{
|
||||
EXPECT_EQ(counter.labelsString(), 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)");
|
||||
}
|
||||
|
||||
TEST_F(AnyCounterTests, operatorAdd)
|
||||
{
|
||||
EXPECT_CALL(mockCounterImpl, add(1));
|
||||
++counter;
|
||||
EXPECT_CALL(mockCounterImpl, add(42));
|
||||
counter += 42;
|
||||
}
|
||||
|
||||
TEST_F(AnyCounterTests, reset)
|
||||
{
|
||||
EXPECT_CALL(mockCounterImpl, set(0));
|
||||
counter.reset();
|
||||
}
|
||||
|
||||
TEST_F(AnyCounterTests, value)
|
||||
{
|
||||
EXPECT_CALL(mockCounterImpl, value()).WillOnce(::testing::Return(42));
|
||||
EXPECT_EQ(counter.value(), 42);
|
||||
}
|
||||
|
||||
struct CounterIntTests : ::testing::Test {
|
||||
CounterInt counter{"test_counter", R"(label1="value1",label2="value2")"};
|
||||
};
|
||||
|
||||
TEST_F(CounterIntTests, operatorAdd)
|
||||
{
|
||||
++counter;
|
||||
counter += 24;
|
||||
EXPECT_EQ(counter.value(), 25);
|
||||
}
|
||||
|
||||
TEST_F(CounterIntTests, reset)
|
||||
{
|
||||
++counter;
|
||||
EXPECT_EQ(counter.value(), 1);
|
||||
counter.reset();
|
||||
EXPECT_EQ(counter.value(), 0);
|
||||
}
|
||||
|
||||
TEST_F(CounterIntTests, multithreadAdd)
|
||||
{
|
||||
static auto constexpr numAdditions = 1000;
|
||||
static auto constexpr numNumberAdditions = 100;
|
||||
static auto constexpr numberToAdd = 11;
|
||||
std::thread thread1([&] {
|
||||
for (int i = 0; i < numAdditions; ++i) {
|
||||
++counter;
|
||||
}
|
||||
});
|
||||
std::thread thread2([&] {
|
||||
for (int i = 0; i < numNumberAdditions; ++i) {
|
||||
counter += numberToAdd;
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
EXPECT_EQ(counter.value(), numAdditions + numNumberAdditions * numberToAdd);
|
||||
}
|
||||
|
||||
struct CounterDoubleTests : ::testing::Test {
|
||||
CounterDouble counter{"test_counter", R"(label1="value1",label2="value2")"};
|
||||
};
|
||||
|
||||
TEST_F(CounterDoubleTests, operatorAdd)
|
||||
{
|
||||
++counter;
|
||||
counter += 24.1234;
|
||||
EXPECT_NEAR(counter.value(), 25.1234, 1e-9);
|
||||
}
|
||||
|
||||
TEST_F(CounterDoubleTests, reset)
|
||||
{
|
||||
++counter;
|
||||
EXPECT_EQ(counter.value(), 1.);
|
||||
counter.reset();
|
||||
EXPECT_EQ(counter.value(), 0.);
|
||||
}
|
||||
|
||||
TEST_F(CounterDoubleTests, multithreadAdd)
|
||||
{
|
||||
static auto constexpr numAdditions = 1000;
|
||||
static auto constexpr numNumberAdditions = 100;
|
||||
static auto constexpr numberToAdd = 11.1234;
|
||||
std::thread thread1([&] {
|
||||
for (int i = 0; i < numAdditions; ++i) {
|
||||
++counter;
|
||||
}
|
||||
});
|
||||
std::thread thread2([&] {
|
||||
for (int i = 0; i < numNumberAdditions; ++i) {
|
||||
counter += numberToAdd;
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
EXPECT_NEAR(counter.value(), numAdditions + numNumberAdditions * numberToAdd, 1e-9);
|
||||
}
|
||||
190
unittests/util/prometheus/GaugeTests.cpp
Normal file
190
unittests/util/prometheus/GaugeTests.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/Gauge.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
struct AnyGaugeTests : ::testing::Test {
|
||||
struct MockGaugeImpl {
|
||||
using ValueType = std::int64_t;
|
||||
MOCK_METHOD(void, add, (ValueType));
|
||||
MOCK_METHOD(void, set, (ValueType));
|
||||
MOCK_METHOD(ValueType, value, (), (const));
|
||||
};
|
||||
|
||||
::testing::StrictMock<MockGaugeImpl> mockGaugeImpl;
|
||||
GaugeInt gauge{"test_gauge", R"(label1="value1",label2="value2")", static_cast<MockGaugeImpl&>(mockGaugeImpl)};
|
||||
};
|
||||
|
||||
TEST_F(AnyGaugeTests, operatorAdd)
|
||||
{
|
||||
EXPECT_CALL(mockGaugeImpl, add(1));
|
||||
++gauge;
|
||||
EXPECT_CALL(mockGaugeImpl, add(42));
|
||||
gauge += 42;
|
||||
}
|
||||
|
||||
TEST_F(AnyGaugeTests, operatorSubstract)
|
||||
{
|
||||
EXPECT_CALL(mockGaugeImpl, add(-1));
|
||||
--gauge;
|
||||
EXPECT_CALL(mockGaugeImpl, add(-42));
|
||||
gauge -= 42;
|
||||
}
|
||||
|
||||
TEST_F(AnyGaugeTests, set)
|
||||
{
|
||||
EXPECT_CALL(mockGaugeImpl, set(42));
|
||||
gauge.set(42);
|
||||
}
|
||||
|
||||
TEST_F(AnyGaugeTests, value)
|
||||
{
|
||||
EXPECT_CALL(mockGaugeImpl, value()).WillOnce(::testing::Return(42));
|
||||
EXPECT_EQ(gauge.value(), 42);
|
||||
}
|
||||
|
||||
struct GaugeIntTests : ::testing::Test {
|
||||
GaugeInt gauge{"test_Gauge", R"(label1="value1",label2="value2")"};
|
||||
};
|
||||
|
||||
TEST_F(GaugeIntTests, operatorAdd)
|
||||
{
|
||||
++gauge;
|
||||
gauge += 24;
|
||||
EXPECT_EQ(gauge.value(), 25);
|
||||
}
|
||||
|
||||
TEST_F(GaugeIntTests, operatorSubstract)
|
||||
{
|
||||
--gauge;
|
||||
EXPECT_EQ(gauge.value(), -1);
|
||||
}
|
||||
|
||||
TEST_F(GaugeIntTests, set)
|
||||
{
|
||||
gauge.set(21);
|
||||
EXPECT_EQ(gauge.value(), 21);
|
||||
}
|
||||
|
||||
TEST_F(GaugeIntTests, multithreadAddAndSubstract)
|
||||
{
|
||||
static constexpr auto numAdditions = 1000;
|
||||
static constexpr auto numNumberAdditions = 100;
|
||||
static constexpr auto numberToAdd = 11;
|
||||
static constexpr auto numSubstractions = 2000;
|
||||
static constexpr auto numNumberSubstractions = 300;
|
||||
static constexpr auto numberToSubstract = 300;
|
||||
std::thread thread1([&] {
|
||||
for (int i = 0; i < numAdditions; ++i) {
|
||||
++gauge;
|
||||
}
|
||||
});
|
||||
std::thread thread2([&] {
|
||||
for (int i = 0; i < numNumberAdditions; ++i) {
|
||||
gauge += numberToAdd;
|
||||
}
|
||||
});
|
||||
std::thread thread3([&] {
|
||||
for (int i = 0; i < numSubstractions; ++i) {
|
||||
--gauge;
|
||||
}
|
||||
});
|
||||
std::thread thread4([&] {
|
||||
for (int i = 0; i < numNumberSubstractions; ++i) {
|
||||
gauge -= numberToSubstract;
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
thread3.join();
|
||||
thread4.join();
|
||||
EXPECT_EQ(
|
||||
gauge.value(),
|
||||
numAdditions + numNumberAdditions * numberToAdd - numSubstractions - numNumberSubstractions * numberToSubstract
|
||||
);
|
||||
}
|
||||
|
||||
struct GaugeDoubleTests : ::testing::Test {
|
||||
GaugeDouble gauge{"test_Gauge", R"(label1="value1",label2="value2")"};
|
||||
};
|
||||
|
||||
TEST_F(GaugeDoubleTests, operatorAdd)
|
||||
{
|
||||
++gauge;
|
||||
gauge += 24.1234;
|
||||
EXPECT_NEAR(gauge.value(), 25.1234, 1e-9);
|
||||
}
|
||||
|
||||
TEST_F(GaugeDoubleTests, operatorSubstract)
|
||||
{
|
||||
--gauge;
|
||||
EXPECT_EQ(gauge.value(), -1.0);
|
||||
}
|
||||
|
||||
TEST_F(GaugeDoubleTests, set)
|
||||
{
|
||||
gauge.set(21.1234);
|
||||
EXPECT_EQ(gauge.value(), 21.1234);
|
||||
}
|
||||
|
||||
TEST_F(GaugeDoubleTests, multithreadAddAndSubstract)
|
||||
{
|
||||
static constexpr auto numAdditions = 1000;
|
||||
static constexpr auto numNumberAdditions = 100;
|
||||
static constexpr auto numberToAdd = 11.1234;
|
||||
static constexpr auto numSubstractions = 2000;
|
||||
static constexpr auto numNumberSubstractions = 300;
|
||||
static constexpr auto numberToSubstract = 300.321;
|
||||
std::thread thread1([&] {
|
||||
for (int i = 0; i < numAdditions; ++i) {
|
||||
++gauge;
|
||||
}
|
||||
});
|
||||
std::thread thread2([&] {
|
||||
for (int i = 0; i < numNumberAdditions; ++i) {
|
||||
gauge += numberToAdd;
|
||||
}
|
||||
});
|
||||
std::thread thread3([&] {
|
||||
for (int i = 0; i < numSubstractions; ++i) {
|
||||
--gauge;
|
||||
}
|
||||
});
|
||||
std::thread thread4([&] {
|
||||
for (int i = 0; i < numNumberSubstractions; ++i) {
|
||||
gauge -= numberToSubstract;
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
thread2.join();
|
||||
thread3.join();
|
||||
thread4.join();
|
||||
EXPECT_NEAR(
|
||||
gauge.value(),
|
||||
numAdditions + numNumberAdditions * numberToAdd - numSubstractions - numNumberSubstractions * numberToSubstract,
|
||||
1e-9
|
||||
);
|
||||
}
|
||||
213
unittests/util/prometheus/HttpTests.cpp
Normal file
213
unittests/util/prometheus/HttpTests.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/Http.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
struct PrometheusCheckRequestTestsParams {
|
||||
std::string testName;
|
||||
http::verb method;
|
||||
std::string target;
|
||||
bool isAdmin;
|
||||
bool prometheusEnabled;
|
||||
bool expected;
|
||||
};
|
||||
|
||||
struct PrometheusCheckRequestTests : public ::testing::TestWithParam<PrometheusCheckRequestTestsParams> {
|
||||
struct NameGenerator {
|
||||
template <class ParamType>
|
||||
std::string
|
||||
operator()(testing::TestParamInfo<ParamType> const& info) const
|
||||
{
|
||||
auto bundle = static_cast<PrometheusCheckRequestTestsParams>(info.param);
|
||||
return bundle.testName;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_P(PrometheusCheckRequestTests, isPrometheusRequest)
|
||||
{
|
||||
PrometheusService::init(util::Config{boost::json::value{{"prometheus_enabled", GetParam().prometheusEnabled}}});
|
||||
boost::beast::http::request<boost::beast::http::string_body> req;
|
||||
req.method(GetParam().method);
|
||||
req.target(GetParam().target);
|
||||
EXPECT_EQ(handlePrometheusRequest(req, GetParam().isAdmin).has_value(), GetParam().expected);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
PrometheusHttpTests,
|
||||
PrometheusCheckRequestTests,
|
||||
::testing::ValuesIn({
|
||||
PrometheusCheckRequestTestsParams{
|
||||
.testName = "validRequest",
|
||||
.method = http::verb::get,
|
||||
.target = "/metrics",
|
||||
.isAdmin = true,
|
||||
.prometheusEnabled = true,
|
||||
.expected = true},
|
||||
PrometheusCheckRequestTestsParams{
|
||||
.testName = "validRequestPrometheusDisabled",
|
||||
.method = http::verb::get,
|
||||
.target = "/metrics",
|
||||
.isAdmin = true,
|
||||
.prometheusEnabled = false,
|
||||
.expected = true},
|
||||
PrometheusCheckRequestTestsParams{
|
||||
.testName = "notAdmin",
|
||||
.method = http::verb::get,
|
||||
.target = "/metrics",
|
||||
.isAdmin = false,
|
||||
.prometheusEnabled = true,
|
||||
.expected = true},
|
||||
PrometheusCheckRequestTestsParams{
|
||||
.testName = "wrongMethod",
|
||||
.method = http::verb::post,
|
||||
.target = "/metrics",
|
||||
.isAdmin = true,
|
||||
.prometheusEnabled = true,
|
||||
.expected = false},
|
||||
PrometheusCheckRequestTestsParams{
|
||||
.testName = "wrongTarget",
|
||||
.method = http::verb::get,
|
||||
.target = "/",
|
||||
.isAdmin = true,
|
||||
.prometheusEnabled = true,
|
||||
.expected = false},
|
||||
}),
|
||||
PrometheusCheckRequestTests::NameGenerator()
|
||||
);
|
||||
|
||||
struct PrometheusHandleRequestTests : ::testing::Test {
|
||||
PrometheusHandleRequestTests()
|
||||
{
|
||||
PrometheusService::init();
|
||||
}
|
||||
http::request<http::string_body> const req{http::verb::get, "/metrics", 11};
|
||||
};
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, emptyResponse)
|
||||
{
|
||||
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->body(), "");
|
||||
}
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, prometheusDisabled)
|
||||
{
|
||||
PrometheusService::init(util::Config(boost::json::value{{"prometheus_enabled", false}}));
|
||||
auto response = handlePrometheusRequest(req, true);
|
||||
ASSERT_TRUE(response.has_value());
|
||||
EXPECT_EQ(response->result(), http::status::forbidden);
|
||||
}
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, notAdmin)
|
||||
{
|
||||
auto response = handlePrometheusRequest(req, false);
|
||||
ASSERT_TRUE(response.has_value());
|
||||
EXPECT_EQ(response->result(), http::status::unauthorized);
|
||||
}
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, responseWithCounter)
|
||||
{
|
||||
auto const counterName = "test_counter";
|
||||
const Labels labels{{{"label1", "value1"}, Label{"label2", "value2"}}};
|
||||
auto const description = "test_description";
|
||||
|
||||
auto& counter = PrometheusService::counterInt(counterName, labels, description);
|
||||
++counter;
|
||||
counter += 3;
|
||||
|
||||
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");
|
||||
auto const expectedBody =
|
||||
fmt::format("# HELP {0} {1}\n# TYPE {0} counter\n{0}{2} 4\n\n", counterName, description, labels.serialize());
|
||||
EXPECT_EQ(response->body(), expectedBody);
|
||||
}
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, responseWithGauge)
|
||||
{
|
||||
auto const gaugeName = "test_gauge";
|
||||
const Labels labels{{{"label2", "value2"}, Label{"label3", "value3"}}};
|
||||
auto const description = "test_description_gauge";
|
||||
|
||||
auto& gauge = PrometheusService::gaugeInt(gaugeName, labels, description);
|
||||
++gauge;
|
||||
gauge -= 3;
|
||||
|
||||
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");
|
||||
auto const expectedBody =
|
||||
fmt::format("# HELP {0} {1}\n# TYPE {0} gauge\n{0}{2} -2\n\n", gaugeName, description, labels.serialize());
|
||||
EXPECT_EQ(response->body(), expectedBody);
|
||||
}
|
||||
|
||||
TEST_F(PrometheusHandleRequestTests, responseWithCounterAndGauge)
|
||||
{
|
||||
auto const counterName = "test_counter";
|
||||
const Labels counterLabels{{{"label1", "value1"}, Label{"label2", "value2"}}};
|
||||
auto const counterDescription = "test_description";
|
||||
|
||||
auto& counter = PrometheusService::counterInt(counterName, counterLabels, counterDescription);
|
||||
++counter;
|
||||
counter += 3;
|
||||
|
||||
auto const gaugeName = "test_gauge";
|
||||
const Labels gaugeLabels{{{"label2", "value2"}, Label{"label3", "value3"}}};
|
||||
auto const gaugeDescription = "test_description_gauge";
|
||||
|
||||
auto& gauge = PrometheusService::gaugeInt(gaugeName, gaugeLabels, gaugeDescription);
|
||||
++gauge;
|
||||
gauge -= 3;
|
||||
|
||||
auto response = handlePrometheusRequest(req, true);
|
||||
|
||||
EXPECT_EQ(response->result(), http::status::ok);
|
||||
EXPECT_EQ(response->operator[](http::field::content_type), "text/plain; version=0.0.4");
|
||||
auto const expectedBody = fmt::format(
|
||||
"# HELP {3} {4}\n# TYPE {3} gauge\n{3}{5} -2\n\n"
|
||||
"# HELP {0} {1}\n# TYPE {0} counter\n{0}{2} 4\n\n",
|
||||
counterName,
|
||||
counterDescription,
|
||||
counterLabels.serialize(),
|
||||
gaugeName,
|
||||
gaugeDescription,
|
||||
gaugeLabels.serialize()
|
||||
);
|
||||
auto const anotherExpectedBody = fmt::format(
|
||||
"# HELP {0} {1}\n# TYPE {0} counter\n{0}{2} 4\n\n"
|
||||
"# HELP {3} {4}\n# TYPE {3} gauge\n{3}{5} -2\n\n",
|
||||
counterName,
|
||||
counterDescription,
|
||||
counterLabels.serialize(),
|
||||
gaugeName,
|
||||
gaugeDescription,
|
||||
gaugeLabels.serialize()
|
||||
);
|
||||
EXPECT_TRUE(response->body() == expectedBody || response->body() == anotherExpectedBody);
|
||||
}
|
||||
54
unittests/util/prometheus/LabelTests.cpp
Normal file
54
unittests/util/prometheus/LabelTests.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/Label.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace util::prometheus;
|
||||
|
||||
TEST(LabelTests, operatorLower)
|
||||
{
|
||||
EXPECT_LT(Label("aaa", "b"), Label("bbb", "a"));
|
||||
EXPECT_LT(Label("name", "a"), Label("name", "b"));
|
||||
}
|
||||
|
||||
TEST(LabelTests, operatorEquals)
|
||||
{
|
||||
EXPECT_EQ(Label("aaa", "b"), Label("aaa", "b"));
|
||||
EXPECT_NE(Label("aaa", "b"), Label("aaa", "c"));
|
||||
EXPECT_NE(Label("aaa", "b"), Label("bbb", "b"));
|
||||
}
|
||||
|
||||
TEST(LabelTests, serialize)
|
||||
{
|
||||
EXPECT_EQ(Label("name", "value").serialize(), R"(name="value")");
|
||||
EXPECT_EQ(Label("name", "value\n").serialize(), R"(name="value\n")");
|
||||
EXPECT_EQ(Label("name", "value\\").serialize(), R"(name="value\\")");
|
||||
EXPECT_EQ(Label("name", "value\"").serialize(), R"(name="value\"")");
|
||||
}
|
||||
|
||||
TEST(LabelsTest, serialize)
|
||||
{
|
||||
EXPECT_EQ(Labels().serialize(), "");
|
||||
EXPECT_EQ(Labels({Label("name", "value")}).serialize(), R"({name="value"})");
|
||||
EXPECT_EQ(
|
||||
Labels({Label("name", "value"), Label("name2", "value2")}).serialize(), R"({name="value",name2="value2"})"
|
||||
);
|
||||
}
|
||||
130
unittests/util/prometheus/MetricsTests.cpp
Normal file
130
unittests/util/prometheus/MetricsTests.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/Metrics.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));
|
||||
};
|
||||
using MetricStrictMock = ::testing::StrictMock<MetricMock>;
|
||||
|
||||
struct MetricBuilderImplMock {
|
||||
MOCK_METHOD(std::unique_ptr<MetricBase>, build, (std::string, std::string, MetricType));
|
||||
};
|
||||
|
||||
::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};
|
||||
};
|
||||
|
||||
TEST_F(MetricsFamilyTest, getters)
|
||||
{
|
||||
EXPECT_EQ(metricsFamily.name(), name);
|
||||
EXPECT_EQ(metricsFamily.type(), type);
|
||||
}
|
||||
|
||||
TEST_F(MetricsFamilyTest, getMetric)
|
||||
{
|
||||
Labels const labels{{{"label1", "value1"}}};
|
||||
std::string const labelsString = labels.serialize();
|
||||
|
||||
EXPECT_CALL(metricBuilderMock, build(name, labelsString, type))
|
||||
.WillOnce(::testing::Return(std::make_unique<MetricStrictMock>(name, labelsString)));
|
||||
|
||||
auto& metric = metricsFamily.getMetric(labels);
|
||||
EXPECT_EQ(metric.name(), name);
|
||||
EXPECT_EQ(metric.labelsString(), labelsString);
|
||||
|
||||
auto* metricMock = dynamic_cast<MetricStrictMock*>(&metric);
|
||||
ASSERT_NE(metricMock, nullptr);
|
||||
EXPECT_EQ(&metricsFamily.getMetric(labels), &metric);
|
||||
|
||||
Labels const labels2{{{"label1", "value2"}}};
|
||||
std::string const labels2String = labels2.serialize();
|
||||
|
||||
EXPECT_CALL(metricBuilderMock, build(name, labels2String, type))
|
||||
.WillOnce(::testing::Return(std::make_unique<MetricStrictMock>(name, labels2String)));
|
||||
|
||||
auto& metric2 = metricsFamily.getMetric(labels2);
|
||||
EXPECT_EQ(metric2.name(), name);
|
||||
EXPECT_EQ(metric2.labelsString(), labels2String);
|
||||
|
||||
auto* metric2Mock = dynamic_cast<MetricStrictMock*>(&metric2);
|
||||
ASSERT_NE(metric2Mock, nullptr);
|
||||
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"; });
|
||||
|
||||
std::string serialized;
|
||||
metricsFamily.serialize(serialized);
|
||||
|
||||
auto const expected =
|
||||
fmt::format("# HELP {0} {1}\n# TYPE {0} {2}\nmetric\nmetric2\n\n", name, description, toString(type));
|
||||
auto const anotherExpected =
|
||||
fmt::format("# HELP {0} {1}\n# TYPE {0} {2}\nmetric2\nmetric\n\n", name, description, toString(type));
|
||||
EXPECT_TRUE(serialized == expected || serialized == anotherExpected);
|
||||
}
|
||||
Reference in New Issue
Block a user