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:
Sergey Kuznetsov
2023-11-22 12:55:06 +00:00
committed by GitHub
parent 8ebe2d6a80
commit b998473673
41 changed files with 2230 additions and 619 deletions

View File

@@ -144,7 +144,10 @@ target_sources (clio PRIVATE
src/util/log/Logger.cpp
src/util/prometheus/Http.cpp
src/util/prometheus/Label.cpp
src/util/prometheus/Metrics.cpp
src/util/prometheus/MetricBase.cpp
src/util/prometheus/MetricBuilder.cpp
src/util/prometheus/MetricsFamily.cpp
src/util/prometheus/OStream.cpp
src/util/prometheus/Prometheus.cpp
src/util/Random.cpp
src/util/Taggable.cpp
@@ -178,9 +181,12 @@ if (tests)
unittests/util/StringUtils.cpp
unittests/util/prometheus/CounterTests.cpp
unittests/util/prometheus/GaugeTests.cpp
unittests/util/prometheus/HistogramTests.cpp
unittests/util/prometheus/HttpTests.cpp
unittests/util/prometheus/LabelTests.cpp
unittests/util/prometheus/MetricsTests.cpp
unittests/util/prometheus/MetricBuilderTests.cpp
unittests/util/prometheus/MetricsFamilyTests.cpp
unittests/util/prometheus/OStreamTests.cpp
# ETL
unittests/etl/ExtractionDataPipeTests.cpp
unittests/etl/ExtractorTests.cpp

View File

@@ -257,7 +257,7 @@ Exactly equal password gains admin rights for the request or a websocket connect
## Prometheus metrics collection
Clio natively supports Prometheus metrics collection. It accepts Prometheus requests on the port configured in `server` section of config.
Prometheus metrics are enabled by default. To disable it add `"prometheus_enabled": false` to the config.
Prometheus metrics are enabled by default. To disable it add `"prometheus": { "enabled": false }` to the config.
It is important to know that clio responds to Prometheus request only if they are admin requests, so Prometheus should be configured to send admin password in header.
There is an example of docker-compose file, Prometheus and Grafana configs in [examples/infrastructure](examples/infrastructure).

View File

@@ -101,7 +101,10 @@
"log_level": "trace"
}
],
"prometheus_enabled": true,
"prometheus": {
"enabled": true,
"compress_reply": true,
},
"log_level": "info",
// Log format (this is the default format)
"log_format": "%TimeStamp% (%SourceLocation%) [%ThreadID%] %Channel%:%Severity% %Message%",

View File

@@ -68,7 +68,7 @@
},
"gridPos": {
"h": 8,
"w": 5,
"w": 3,
"x": 0,
"y": 0
},
@@ -105,102 +105,6 @@
"title": "Service state",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 7,
"x": 5,
"y": 0
},
"id": 7,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "scrape_duration_seconds{job=\"clio\"}",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Prometheus Request Processing Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
@@ -262,8 +166,8 @@
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"w": 9,
"x": 3,
"y": 0
},
"id": 2,
@@ -296,102 +200,6 @@
"title": "Work Queue Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "µs"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 8
},
"id": 10,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "rpc_method_duration_us{job=\"clio\"}",
"instant": false,
"legendFormat": "{{method}}",
"range": true,
"refId": "A"
}
],
"title": "RPC Method Call Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
@@ -455,7 +263,7 @@
"h": 8,
"w": 12,
"x": 12,
"y": 8
"y": 0
},
"id": 9,
"options": {
@@ -550,9 +358,9 @@
"h": 8,
"w": 12,
"x": 0,
"y": 16
"y": 8
},
"id": 8,
"id": 11,
"options": {
"legend": {
"calcs": [],
@@ -572,14 +380,206 @@
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "rate(rpc_error_total_number{job=\"clio\"}[$__rate_interval])",
"expr": "subscriptions_current_number{job=\"clio\"}",
"instant": false,
"legendFormat": "{{error_type}}",
"legendFormat": "{{collection}}{{stream}}",
"range": true,
"refId": "A"
}
],
"title": "RPC Error Rate",
"title": "Subscriptions Number",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 8
},
"id": 6,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "sum(increase(ledger_cache_counter_total_number{job=\"clio\",type=\"cache_hit\"}[1m])) / sum(increase(ledger_cache_counter_total_number{job=\"clio\",type=\"request\"}[1m]))",
"hide": false,
"instant": false,
"legendFormat": "ledger cache hit rate",
"range": true,
"refId": "A"
}
],
"title": "Ledger Cache Hit Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "µs"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 16
},
"id": 10,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "rpc_method_duration_us{job=\"clio\"}",
"instant": false,
"legendFormat": "{{method}}",
"range": true,
"refId": "A"
}
],
"title": "RPC Method Call Duration",
"type": "timeseries"
},
{
@@ -647,13 +647,13 @@
"x": 12,
"y": 16
},
"id": 6,
"id": 8,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
"showLegend": true
},
"tooltip": {
"mode": "single",
@@ -667,15 +667,14 @@
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "sum(increase(ledger_cache_counter_total_number{job=\"clio\",type=\"cache_hit\"}[1m])) / sum(increase(ledger_cache_counter_total_number{job=\"clio\",type=\"request\"}[1m]))",
"hide": false,
"expr": "rate(rpc_error_total_number{job=\"clio\"}[$__rate_interval])",
"instant": false,
"legendFormat": "ledger cache hit rate",
"legendFormat": "{{error_type}}",
"range": true,
"refId": "A"
}
],
"title": "Ledger Cache Hit Rate",
"title": "RPC Error Rate",
"type": "timeseries"
},
{
@@ -791,7 +790,7 @@
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
@@ -800,6 +799,9 @@
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineStyle": {
"fill": "solid"
},
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
@@ -828,7 +830,8 @@
"value": 80
}
]
}
},
"unit": "ms"
},
"overrides": []
},
@@ -838,7 +841,7 @@
"x": 12,
"y": 24
},
"id": 11,
"id": 12,
"options": {
"legend": {
"calcs": [],
@@ -851,6 +854,7 @@
"sort": "none"
}
},
"pluginVersion": "10.2.0",
"targets": [
{
"datasource": {
@@ -858,14 +862,52 @@
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "subscriptions_current_number{job=\"clio\"}",
"expr": "histogram_quantile(0.50, sum(rate(backend_duration_milliseconds_histogram_bucket{job=\"clio\"}[$__interval])) by (le, operation))",
"hide": false,
"instant": false,
"legendFormat": "{{collection}}{{stream}}",
"legendFormat": "{{operation}} 0.5 percentile",
"range": true,
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "histogram_quantile(0.75, sum(rate(backend_duration_milliseconds_histogram_bucket{job=\"clio\"}[$__interval])) by (le, operation))",
"hide": false,
"instant": false,
"legendFormat": "{{operation}} 0.75 percentile",
"range": true,
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "histogram_quantile(0.95, sum(rate(backend_duration_milliseconds_histogram_bucket{job=\"clio\"}[$__interval])) by (le, operation))",
"hide": false,
"instant": false,
"legendFormat": "{{operation}} 0.95 percentile",
"range": true,
"refId": "C"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"expr": "",
"hide": false,
"instant": false,
"range": true,
"refId": "D"
}
],
"title": "Subscriptions Number",
"title": "DB operation duration",
"type": "timeseries"
},
{
@@ -1081,6 +1123,102 @@
],
"title": "DB Pending operations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 10,
"x": 0,
"y": 40
},
"id": 7,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"editorMode": "code",
"expr": "scrape_duration_seconds{job=\"clio\"}",
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A"
}
],
"title": "Prometheus Request Processing Time",
"type": "timeseries"
}
],
"refresh": "5s",
@@ -1097,6 +1235,6 @@
"timezone": "",
"title": "Clio",
"uid": "aeaae84e-c194-47b2-ad65-86e45eebb815",
"version": 1,
"version": 3,
"weekStart": ""
}

View File

@@ -24,6 +24,18 @@
namespace data {
namespace {
std::vector<std::int64_t> const histogramBuckets{1, 2, 5, 10, 20, 50, 100, 200, 500, 700, 1000};
std::int64_t
durationInMillisecondsSince(std::chrono::steady_clock::time_point const startTime)
{
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - startTime).count();
}
} // namespace
using namespace util::prometheus;
BackendCounters::BackendCounters()
@@ -44,6 +56,18 @@ BackendCounters::BackendCounters()
))
, asyncWriteCounters_{"write_async"}
, asyncReadCounters_{"read_async"}
, readDurationHistogram_(PrometheusService::histogramInt(
"backend_duration_milliseconds_histogram",
Labels({Label{"operation", "read"}}),
histogramBuckets,
"The duration of backend read operations including retries"
))
, writeDurationHistogram_(PrometheusService::histogramInt(
"backend_duration_milliseconds_histogram",
Labels({Label{"operation", "write"}}),
histogramBuckets,
"The duration of backend write operations including retries"
))
{
}
@@ -61,9 +85,10 @@ BackendCounters::registerTooBusy()
}
void
BackendCounters::registerWriteSync()
BackendCounters::registerWriteSync(std::chrono::steady_clock::time_point const startTime)
{
++writeSyncCounter_.get();
writeDurationHistogram_.get().observe(durationInMillisecondsSince(startTime));
}
void
@@ -79,9 +104,11 @@ BackendCounters::registerWriteStarted()
}
void
BackendCounters::registerWriteFinished()
BackendCounters::registerWriteFinished(std::chrono::steady_clock::time_point const startTime)
{
asyncWriteCounters_.registerFinished(1u);
auto const duration = durationInMillisecondsSince(startTime);
writeDurationHistogram_.get().observe(duration);
}
void
@@ -97,9 +124,12 @@ BackendCounters::registerReadStarted(std::uint64_t const count)
}
void
BackendCounters::registerReadFinished(std::uint64_t const count)
BackendCounters::registerReadFinished(std::chrono::steady_clock::time_point const startTime, std::uint64_t const count)
{
asyncReadCounters_.registerFinished(count);
auto const duration = durationInMillisecondsSince(startTime);
for (std::uint64_t i = 0; i < count; ++i)
readDurationHistogram_.get().observe(duration);
}
void

View File

@@ -33,23 +33,43 @@ namespace data {
/**
* @brief A concept for a class that can be used to count backend operations.
*/
// clang-format off
template <typename T>
concept SomeBackendCounters = requires(T a) {
typename T::PtrType;
{ a.registerTooBusy() } -> std::same_as<void>;
{ a.registerWriteSync() } -> std::same_as<void>;
{ a.registerWriteSyncRetry() } -> std::same_as<void>;
{ a.registerWriteStarted() } -> std::same_as<void>;
{ a.registerWriteFinished() } -> std::same_as<void>;
{ a.registerWriteRetry() } -> std::same_as<void>;
{ a.registerReadStarted(std::uint64_t{}) } -> std::same_as<void>;
{ a.registerReadFinished(std::uint64_t{}) } -> std::same_as<void>;
{ a.registerReadRetry(std::uint64_t{}) } -> std::same_as<void>;
{ a.registerReadError(std::uint64_t{}) } -> std::same_as<void>;
{ a.report() } -> std::same_as<boost::json::object>;
{
a.registerTooBusy()
} -> std::same_as<void>;
{
a.registerWriteSync(std::chrono::steady_clock::time_point{})
} -> std::same_as<void>;
{
a.registerWriteSyncRetry()
} -> std::same_as<void>;
{
a.registerWriteStarted()
} -> std::same_as<void>;
{
a.registerWriteFinished(std::chrono::steady_clock::time_point{})
} -> std::same_as<void>;
{
a.registerWriteRetry()
} -> std::same_as<void>;
{
a.registerReadStarted(std::uint64_t{})
} -> std::same_as<void>;
{
a.registerReadFinished(std::chrono::steady_clock::time_point{}, std::uint64_t{})
} -> std::same_as<void>;
{
a.registerReadRetry(std::uint64_t{})
} -> std::same_as<void>;
{
a.registerReadError(std::uint64_t{})
} -> std::same_as<void>;
{
a.report()
} -> std::same_as<boost::json::object>;
};
// clang-format on
/**
* @brief Holds statistics about the backend.
@@ -67,7 +87,7 @@ public:
registerTooBusy();
void
registerWriteSync();
registerWriteSync(std::chrono::steady_clock::time_point startTime);
void
registerWriteSyncRetry();
@@ -76,7 +96,7 @@ public:
registerWriteStarted();
void
registerWriteFinished();
registerWriteFinished(std::chrono::steady_clock::time_point startTime);
void
registerWriteRetry();
@@ -85,7 +105,7 @@ public:
registerReadStarted(std::uint64_t count = 1u);
void
registerReadFinished(std::uint64_t count = 1u);
registerReadFinished(std::chrono::steady_clock::time_point startTime, std::uint64_t count = 1u);
void
registerReadRetry(std::uint64_t count = 1u);
@@ -133,6 +153,9 @@ private:
AsyncOperationCounters asyncWriteCounters_{"write_async"};
AsyncOperationCounters asyncReadCounters_{"read_async"};
std::reference_wrapper<util::prometheus::HistogramInt> readDurationHistogram_;
std::reference_wrapper<util::prometheus::HistogramInt> writeDurationHistogram_;
};
} // namespace data

View File

@@ -141,10 +141,11 @@ public:
ResultOrErrorType
writeSync(StatementType const& statement)
{
counters_->registerWriteSync();
auto const startTime = std::chrono::steady_clock::now();
while (true) {
auto res = handle_.get().execute(statement);
if (res) {
counters_->registerWriteSync(startTime);
return res;
}
@@ -179,6 +180,8 @@ public:
void
write(PreparedStatementType const& preparedStatement, Args&&... args)
{
auto const startTime = std::chrono::steady_clock::now();
auto statement = preparedStatement.bind(std::forward<Args>(args)...);
incrementOutstandingRequestCount();
@@ -188,10 +191,10 @@ public:
ioc_,
handle_,
std::move(statement),
[this](auto const&) {
[this, startTime](auto const&) {
decrementOutstandingRequestCount();
counters_->registerWriteFinished();
counters_->registerWriteFinished(startTime);
},
[this]() { counters_->registerWriteRetry(); }
);
@@ -211,6 +214,8 @@ public:
if (statements.empty())
return;
auto const startTime = std::chrono::steady_clock::now();
incrementOutstandingRequestCount();
counters_->registerWriteStarted();
@@ -219,9 +224,9 @@ public:
ioc_,
handle_,
std::move(statements),
[this](auto const&) {
[this, startTime](auto const&) {
decrementOutstandingRequestCount();
counters_->registerWriteFinished();
counters_->registerWriteFinished(startTime);
},
[this]() { counters_->registerWriteRetry(); }
);
@@ -258,6 +263,8 @@ public:
[[maybe_unused]] ResultOrErrorType
read(CompletionTokenType token, std::vector<StatementType> const& statements)
{
auto const startTime = std::chrono::steady_clock::now();
auto const numStatements = statements.size();
std::optional<FutureWithCallbackType> future;
counters_->registerReadStarted(numStatements);
@@ -283,7 +290,7 @@ public:
numReadRequestsOutstanding_ -= numStatements;
if (res) {
counters_->registerReadFinished(numStatements);
counters_->registerReadFinished(startTime, numStatements);
return res;
}
@@ -311,6 +318,8 @@ public:
[[maybe_unused]] ResultOrErrorType
read(CompletionTokenType token, StatementType const& statement)
{
auto const startTime = std::chrono::steady_clock::now();
std::optional<FutureWithCallbackType> future;
counters_->registerReadStarted();
@@ -334,7 +343,7 @@ public:
--numReadRequestsOutstanding_;
if (res) {
counters_->registerReadFinished();
counters_->registerReadFinished(startTime);
return res;
}
@@ -363,6 +372,8 @@ public:
std::vector<ResultType>
readEach(CompletionTokenType token, std::vector<StatementType> const& statements)
{
auto const startTime = std::chrono::steady_clock::now();
std::atomic_uint64_t errorsCount = 0u;
std::atomic_int numOutstanding = statements.size();
numReadRequestsOutstanding_ += statements.size();
@@ -403,10 +414,10 @@ public:
if (errorsCount > 0) {
ASSERT(errorsCount <= statements.size(), "Errors number cannot exceed statements number");
counters_->registerReadError(errorsCount);
counters_->registerReadFinished(statements.size() - errorsCount);
counters_->registerReadFinished(startTime, statements.size() - errorsCount);
throw DatabaseTimeout{};
}
counters_->registerReadFinished(statements.size());
counters_->registerReadFinished(startTime, statements.size());
std::vector<ResultType> results;
results.reserve(futures.size());

View File

@@ -36,11 +36,14 @@ ETLService::runETLPipeline(uint32_t startSequence, uint32_t numExtractors)
LOG(log_.debug()) << "Starting etl pipeline";
state_.isWriting = true;
auto rng = backend_->hardFetchLedgerRangeNoThrow();
if (!rng || rng->maxSequence < startSequence - 1) {
assert(false);
throw std::runtime_error("runETLPipeline: parent ledger is null");
}
auto const rng = backend_->hardFetchLedgerRangeNoThrow();
ASSERT(rng.has_value(), "Parent ledger range can't be null");
ASSERT(
rng->maxSequence < startSequence - 1,
"Got not parent ledger. rnd->maxSequence = {}, startSequence = {}",
rng->maxSequence,
startSequence
);
auto const begin = std::chrono::system_clock::now();
auto extractors = std::vector<std::unique_ptr<ExtractorType>>{};

View File

@@ -25,6 +25,7 @@
#include <etl/impl/AsyncData.h>
#include <etl/impl/ForwardCache.h>
#include <feed/SubscriptionManager.h>
#include <util/Assert.h>
#include <util/config/Config.h>
#include <util/log/Logger.h>
@@ -485,7 +486,7 @@ public:
std::vector<std::string> edgeKeys;
while (numFinished < calls.size() && cq.Next(&tag, &ok)) {
assert(tag);
ASSERT(tag != nullptr, "Tag can't be null.");
auto ptr = static_cast<etl::detail::AsyncCallData*>(tag);
if (!ok) {

View File

@@ -26,6 +26,7 @@
#include <web/interface/ConnectionBase.h>
#include <ripple/protocol/LedgerHeader.h>
#include <fmt/format.h>
#include <memory>

104
src/util/Atomic.h Normal file
View File

@@ -0,0 +1,104 @@
//------------------------------------------------------------------------------
/*
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/Concepts.h>
#include <atomic>
#include <memory>
namespace util {
/**
* @brief Atomic wrapper for integral and floating point types
*/
template <SomeNumberType NumberType>
class Atomic {
public:
using ValueType = NumberType;
Atomic() = default;
Atomic(ValueType const value) : value_(value)
{
}
~Atomic() = default;
// Copy and move constructors and assignment operators are not allowed for atomics
Atomic(Atomic const&) = delete;
Atomic(Atomic&&) = delete;
Atomic&
operator=(Atomic const&) = delete;
Atomic&
operator=(Atomic&&) = delete;
/**
* @brief Add a value to the current value
*
* @param value The value to add
*/
void
add(ValueType const value)
{
if constexpr (std::is_integral_v<ValueType>) {
value_.fetch_add(value);
} else {
#if __cpp_lib_atomic_float >= 201711L
value_.fetch_add(value);
#else
// Workaround for atomic float not being supported by the standard library
// compare_exchange_weak returns false if the value is not exchanged and updates the current value
auto current = value_.load();
while (!value_.compare_exchange_weak(current, current + value)) {
}
#endif
}
}
/**
* @brief Update the current value to the new value
*
* @param value The new value
*/
void
set(ValueType const value)
{
value_ = value;
}
/**
* @brief Get the current value
*
* @return ValueType The current value
*/
ValueType
value() const
{
return value_;
}
private:
std::atomic<ValueType> value_{0};
};
template <SomeNumberType NumberType>
using AtomicPtr = std::unique_ptr<Atomic<NumberType>>;
} // namespace util

29
src/util/Concepts.h Normal file
View File

@@ -0,0 +1,29 @@
//------------------------------------------------------------------------------
/*
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 <concepts>
namespace util {
template <typename T>
concept SomeNumberType = std::is_arithmetic_v<T> && !std::is_same_v<T, bool> && !std::is_const_v<T>;
} // namespace util

View File

@@ -20,7 +20,7 @@
#pragma once
#include <util/Assert.h>
#include <util/prometheus/Metrics.h>
#include <util/prometheus/MetricBase.h>
#include <util/prometheus/impl/AnyCounterBase.h>
namespace util::prometheus {
@@ -28,7 +28,7 @@ namespace util::prometheus {
/**
* @brief A prometheus counter metric implementation. It can only be increased or be reset to zero.
*/
template <detail::SomeNumberType NumberType>
template <SomeNumberType NumberType>
struct AnyCounter : MetricBase, detail::AnyCounterBase<NumberType> {
using ValueType = NumberType;
@@ -91,12 +91,12 @@ struct AnyCounter : MetricBase, detail::AnyCounterBase<NumberType> {
/**
* @brief Serialize the counter to a string in prometheus format (i.e. name{labels} value)
*
* @param result The string to serialize into
* @param stream The stream to serialize into
*/
void
serializeValue(std::string& result) const override
serializeValue(OStream& stream) const override
{
fmt::format_to(std::back_inserter(result), "{}{} {}", this->name(), this->labelsString(), value());
stream << name() << labelsString() << ' ' << value();
}
};

View File

@@ -19,7 +19,7 @@
#pragma once
#include <util/prometheus/Metrics.h>
#include <util/prometheus/MetricBase.h>
#include <util/prometheus/impl/AnyCounterBase.h>
namespace util::prometheus {
@@ -27,7 +27,7 @@ namespace util::prometheus {
/**
* @brief A prometheus gauge metric implementation. It can be increased, decreased or set to a value.
*/
template <detail::SomeNumberType NumberType>
template <SomeNumberType NumberType>
struct AnyGauge : MetricBase, detail::AnyCounterBase<NumberType> {
using ValueType = NumberType;
@@ -113,12 +113,12 @@ struct AnyGauge : MetricBase, detail::AnyCounterBase<NumberType> {
/**
* @brief Serialize the counter to a string in prometheus format (i.e. name{labels} value)
*
* @param result The string to serialize into
* @param stream The stream to serialize into
*/
void
serializeValue(std::string& result) const override
serializeValue(OStream& stream) const override
{
fmt::format_to(std::back_inserter(result), "{}{} {}", this->name(), this->labelsString(), value());
stream << name() << labelsString() << ' ' << value();
}
};

View File

@@ -0,0 +1,128 @@
//------------------------------------------------------------------------------
/*
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.h>
#include <util/prometheus/MetricBase.h>
#include <util/prometheus/impl/HistogramImpl.h>
namespace util::prometheus {
/**
* @brief A Prometheus histogram metric with a generic value type
*/
template <SomeNumberType NumberType>
class AnyHistogram : public MetricBase {
public:
using ValueType = NumberType;
using Buckets = std::vector<NumberType>;
/**
* @brief Construct a new Histogram object
*
* @param name The name of the metric
* @param labelsString The labels of the metric in serialized format, e.g. {name="value",name2="value2"}
* @param buckets The buckets of the histogram
* @param impl The implementation of the histogram (has default value and need to be specified only for testing)
*/
template <detail::SomeHistogramImpl ImplType = detail::HistogramImpl<ValueType>>
requires std::same_as<ValueType, typename std::remove_cvref_t<ImplType>::ValueType>
AnyHistogram(std::string name, std::string labelsString, Buckets const& buckets, ImplType&& impl = ImplType{})
: MetricBase(std::move(name), std::move(labelsString))
, pimpl_(std::make_unique<Model<ImplType>>(std::forward<ImplType>(impl)))
{
ASSERT(!buckets.empty(), "Histogram must have at least one bucket.");
ASSERT(std::is_sorted(buckets.begin(), buckets.end()), "Buckets for histogra must be sorted.");
pimpl_->setBuckets(buckets);
}
/**
* @brief Add a value to the histogram
*
* @param value The value to add
*/
void
observe(ValueType const value)
{
pimpl_->observe(value);
}
/**
* @brief Serialize the metric to a string in Prometheus format
*
* @param stream The stream to serialize into
*/
void
serializeValue(OStream& stream) const override
{
pimpl_->serializeValue(name(), labelsString(), stream);
}
private:
struct Concept {
virtual ~Concept() = default;
virtual void observe(NumberType) = 0;
virtual void
setBuckets(Buckets const& buckets) = 0;
virtual void
serializeValue(std::string const& name, std::string const& labelsString, OStream&) const = 0;
};
template <detail::SomeHistogramImpl ImplType>
requires std::same_as<NumberType, typename std::remove_cvref_t<ImplType>::ValueType>
struct Model : Concept {
template <typename SomeImplType>
requires std::same_as<SomeImplType, ImplType>
Model(SomeImplType&& impl) : impl_(std::forward<SomeImplType>(impl))
{
}
void
observe(NumberType value) override
{
impl_.observe(value);
}
void
setBuckets(Buckets const& buckets) override
{
impl_.setBuckets(buckets);
}
void
serializeValue(std::string const& name, std::string const& labelsString, OStream& stream) const override
{
impl_.serializeValue(name, labelsString, stream);
}
private:
ImplType impl_;
};
std::unique_ptr<Concept> pimpl_;
};
using HistogramInt = AnyHistogram<std::int64_t>;
using HistogramDouble = AnyHistogram<double>;
} // namespace util::prometheus

View File

@@ -54,8 +54,14 @@ handlePrometheusRequest(http::request<http::string_body> const& req, bool const
}
auto response = http::response<http::string_body>(http::status::ok, req.version());
response.set(http::field::content_type, "text/plain; version=0.0.4");
response.body() = PrometheusService::collectMetrics(); // TODO(#932): add gzip compression
response.body() = PrometheusService::collectMetrics();
if (PrometheusService::compressReplyEnabled())
response.set(http::field::content_encoding, "gzip");
return response;
}

View File

@@ -0,0 +1,74 @@
//------------------------------------------------------------------------------
/*
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/MetricBase.h>
#include <util/Assert.h>
namespace util::prometheus {
MetricBase::MetricBase(std::string name, std::string labelsString)
: name_(std::move(name)), labelsString_(std::move(labelsString))
{
}
OStream&
operator<<(OStream& stream, MetricBase const& metricBase)
{
metricBase.serializeValue(stream);
return stream;
}
char const*
toString(MetricType type)
{
switch (type) {
case MetricType::COUNTER_INT:
[[fallthrough]];
case MetricType::COUNTER_DOUBLE:
return "counter";
case MetricType::GAUGE_INT:
[[fallthrough]];
case MetricType::GAUGE_DOUBLE:
return "gauge";
case MetricType::HISTOGRAM_INT:
[[fallthrough]];
case MetricType::HISTOGRAM_DOUBLE:
return "histogram";
case MetricType::SUMMARY:
return "summary";
default:
ASSERT(false, "Unknown metric {}.", static_cast<int>(type));
}
return "";
}
std::string const&
MetricBase::name() const
{
return name_;
}
std::string const&
MetricBase::labelsString() const
{
return labelsString_;
}
} // namespace util::prometheus

View File

@@ -0,0 +1,90 @@
//------------------------------------------------------------------------------
/*
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/Label.h>
#include <util/prometheus/OStream.h>
namespace util::prometheus {
/**
* @brief Base class for a Prometheus metric containing a name and labels
*/
class MetricBase {
public:
MetricBase(std::string name, std::string labelsString);
MetricBase(MetricBase const&) = delete;
MetricBase(MetricBase&&) = default;
MetricBase&
operator=(MetricBase const&) = delete;
MetricBase&
operator=(MetricBase&&) = default;
virtual ~MetricBase() = default;
/**
* @brief Serialize the metric to a string in Prometheus format
*
* @param stream The stream to serialize into
* @param metricBase The metric to serialize
*/
friend OStream&
operator<<(OStream& stream, MetricBase const& metricBase);
/**
* @brief Get the name of the metric
*/
std::string const&
name() const;
/**
* @brief Get the labels of the metric in serialized format, e.g. {name="value",name2="value2"}
*/
std::string const&
labelsString() const;
protected:
/**
* @brief Interface to serialize the value of the metric
*
* @return The serialized value
*/
virtual void
serializeValue(OStream& stream) const = 0;
private:
std::string name_;
std::string labelsString_;
};
enum class MetricType {
COUNTER_INT,
COUNTER_DOUBLE,
GAUGE_INT,
GAUGE_DOUBLE,
HISTOGRAM_INT,
HISTOGRAM_DOUBLE,
SUMMARY
};
char const*
toString(MetricType type);
} // namespace util::prometheus

View File

@@ -0,0 +1,122 @@
//------------------------------------------------------------------------------
/*
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/Assert.h>
#include <util/prometheus/Counter.h>
#include <util/prometheus/Gauge.h>
#include <util/prometheus/Histogram.h>
#include <util/prometheus/MetricBuilder.h>
namespace util::prometheus {
std::unique_ptr<MetricBase>
MetricBuilder::operator()(
std::string name,
std::string labelsString,
MetricType const type,
std::vector<std::int64_t> const& buckets
)
{
ASSERT(type != MetricType::HISTOGRAM_DOUBLE, "Wrong metric type. Probably wrong bucket type was used.");
if (type == MetricType::HISTOGRAM_INT) {
return makeHistogram(std::move(name), std::move(labelsString), type, buckets);
}
ASSERT(buckets.empty(), "Buckets must be empty for non-histogram types.");
return makeMetric(std::move(name), std::move(labelsString), type);
}
std::unique_ptr<MetricBase>
MetricBuilder::operator()(
std::string name,
std::string labelsString,
MetricType const type,
std::vector<double> const& buckets
)
{
ASSERT(type == MetricType::HISTOGRAM_DOUBLE, "This method is for HISTOGRAM_DOUBLE only.");
return makeHistogram(std::move(name), std::move(labelsString), type, buckets);
}
std::unique_ptr<MetricBase>
MetricBuilder::makeMetric(std::string name, std::string labelsString, MetricType const type)
{
switch (type) {
case MetricType::COUNTER_INT:
return std::make_unique<CounterInt>(name, labelsString);
case MetricType::COUNTER_DOUBLE:
return std::make_unique<CounterDouble>(name, labelsString);
case MetricType::GAUGE_INT:
return std::make_unique<GaugeInt>(name, labelsString);
case MetricType::GAUGE_DOUBLE:
return std::make_unique<GaugeDouble>(name, labelsString);
case MetricType::HISTOGRAM_INT:
[[fallthrough]];
case MetricType::HISTOGRAM_DOUBLE:
[[fallthrough]];
case MetricType::SUMMARY:
[[fallthrough]];
default:
ASSERT(false, "Unknown metric type: {}", static_cast<int>(type));
}
return nullptr;
}
template <typename ValueType>
requires std::same_as<ValueType, std::int64_t> || std::same_as<ValueType, double>
std::unique_ptr<MetricBase>
MetricBuilder::makeHistogram(
std::string name,
std::string labelsString,
MetricType type,
std::vector<ValueType> const& buckets
)
{
switch (type) {
case MetricType::HISTOGRAM_INT: {
if constexpr (std::same_as<ValueType, std::int64_t>) {
return std::make_unique<HistogramInt>(std::move(name), std::move(labelsString), buckets);
} else {
ASSERT(false, "Wrong bucket type for HISTOGRAM_INT.)");
break;
}
}
case MetricType::HISTOGRAM_DOUBLE:
if constexpr (std::same_as<ValueType, double>) {
return std::make_unique<HistogramDouble>(std::move(name), std::move(labelsString), buckets);
} else {
ASSERT(false, "Wrong bucket type for HISTOGRAM_DOUBLE.");
break;
}
case MetricType::COUNTER_INT:
[[fallthrough]];
case MetricType::COUNTER_DOUBLE:
[[fallthrough]];
case MetricType::GAUGE_INT:
[[fallthrough]];
case MetricType::GAUGE_DOUBLE:
[[fallthrough]];
case MetricType::SUMMARY:
[[fallthrough]];
default:
ASSERT(false, "Unknown metric type: {}", static_cast<int>(type));
}
return nullptr;
}
} // namespace util::prometheus

View File

@@ -0,0 +1,85 @@
//------------------------------------------------------------------------------
/*
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/MetricBase.h>
namespace util::prometheus {
/**
* @brief Interface to create a metric
*/
struct MetricBuilderInterface {
virtual ~MetricBuilderInterface() = default;
/**
* @brief Create a metric
*
* @param name The name of the metric
* @param labelsString The labels of the metric in serialized format, e.g. {name="value",name2="value2"}
* @param type The type of the metric
* @param buckets The buckets of the int based histogram. It is ignored for other metric types
* @return The metric
*/
virtual std::unique_ptr<MetricBase>
operator()(
std::string name,
std::string labelsString,
MetricType type,
std::vector<std::int64_t> const& buckets = {}
) = 0;
/**
* @brief Create a metric double based histogram
*
* @param name The name of the metric
* @param labelsString The labels of the metric in serialized format, e.g. {name="value",name2="value2"}
* @param type The type of the metric. Must be HISOTGRAM_DOUBLE
* @param buckets The buckets of the histogram
* @return Double based histogram
*/
virtual std::unique_ptr<MetricBase>
operator()(std::string name, std::string labelsString, MetricType type, std::vector<double> const& buckets) = 0;
};
class MetricBuilder : public MetricBuilderInterface {
public:
std::unique_ptr<MetricBase>
operator()(
std::string name,
std::string labelsString,
MetricType type,
std::vector<std::int64_t> const& buckets = {}
) override;
std::unique_ptr<MetricBase>
operator()(std::string name, std::string labelsString, MetricType type, std::vector<double> const& buckets)
override;
private:
std::unique_ptr<MetricBase> static makeMetric(std::string name, std::string labelsString, MetricType type);
template <typename ValueType>
requires std::same_as<ValueType, std::int64_t> || std::same_as<ValueType, double>
std::unique_ptr<MetricBase>
makeHistogram(std::string name, std::string labelsString, MetricType type, std::vector<ValueType> const& buckets);
};
} // namespace util::prometheus

View File

@@ -1,140 +0,0 @@
//------------------------------------------------------------------------------
/*
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>
namespace util::prometheus {
MetricBase::MetricBase(std::string name, std::string labelsString)
: name_(std::move(name)), labelsString_(std::move(labelsString))
{
}
void
MetricBase::serialize(std::string& s) const
{
serializeValue(s);
}
char const*
toString(MetricType type)
{
switch (type) {
case MetricType::COUNTER_INT:
[[fallthrough]];
case MetricType::COUNTER_DOUBLE:
return "counter";
case MetricType::GAUGE_INT:
[[fallthrough]];
case MetricType::GAUGE_DOUBLE:
return "gauge";
case MetricType::HISTOGRAM:
return "histogram";
case MetricType::SUMMARY:
return "summary";
default:
ASSERT(false, "Unknown metric type: {}", static_cast<int>(type));
}
return "";
}
std::string const&
MetricBase::name() const
{
return name_;
}
std::string const&
MetricBase::labelsString() const
{
return labelsString_;
}
MetricsFamily::MetricBuilder MetricsFamily::defaultMetricBuilder =
[](std::string name, std::string labelsString, MetricType type) -> std::unique_ptr<MetricBase> {
switch (type) {
case MetricType::COUNTER_INT:
return std::make_unique<CounterInt>(name, labelsString);
case MetricType::COUNTER_DOUBLE:
return std::make_unique<CounterDouble>(name, labelsString);
case MetricType::GAUGE_INT:
return std::make_unique<GaugeInt>(name, labelsString);
case MetricType::GAUGE_DOUBLE:
return std::make_unique<GaugeDouble>(name, labelsString);
case MetricType::SUMMARY:
[[fallthrough]];
case MetricType::HISTOGRAM:
[[fallthrough]];
default:
ASSERT(false, "Unknown metric type: {}", static_cast<int>(type));
}
return nullptr;
};
MetricsFamily::MetricsFamily(
std::string name,
std::optional<std::string> description,
MetricType type,
MetricBuilder& metricBuilder
)
: name_(std::move(name)), description_(std::move(description)), type_(type), metricBuilder_(metricBuilder)
{
}
MetricBase&
MetricsFamily::getMetric(Labels labels)
{
auto labelsString = labels.serialize();
auto it = metrics_.find(labelsString);
if (it == metrics_.end()) {
auto metric = metricBuilder_(name(), labelsString, type());
auto [it2, success] = metrics_.emplace(std::move(labelsString), std::move(metric));
it = it2;
}
return *it->second;
}
void
MetricsFamily::serialize(std::string& result) const
{
if (description_)
fmt::format_to(std::back_inserter(result), "# HELP {} {}\n", name_, *description_);
fmt::format_to(std::back_inserter(result), "# TYPE {} {}\n", name_, toString(type()));
for (auto const& [labelsString, metric] : metrics_) {
metric->serialize(result);
result.push_back('\n');
}
result.push_back('\n');
}
std::string const&
MetricsFamily::name() const
{
return name_;
}
MetricType
MetricsFamily::type() const
{
return type_;
}
} // namespace util::prometheus

View File

@@ -0,0 +1,93 @@
//------------------------------------------------------------------------------
/*
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/MetricsFamily.h>
#include <util/Assert.h>
namespace util::prometheus {
std::unique_ptr<MetricBuilderInterface> MetricsFamily::defaultMetricBuilder = std::make_unique<MetricBuilder>();
MetricsFamily::MetricsFamily(
std::string name,
std::optional<std::string> description,
MetricType type,
MetricBuilderInterface& metricBuilder
)
: name_(std::move(name)), description_(std::move(description)), type_(type), metricBuilder_(metricBuilder)
{
}
MetricBase&
MetricsFamily::getMetric(Labels labels, std::vector<std::int64_t> const& buckets)
{
return getMetricImpl(std::move(labels), buckets);
}
MetricBase&
MetricsFamily::getMetric(Labels labels, std::vector<double> const& buckets)
{
ASSERT(type_ == MetricType::HISTOGRAM_DOUBLE, "This method is for HISTOGRAM_DOUBLE only.");
return getMetricImpl(std::move(labels), buckets);
}
OStream&
operator<<(OStream& stream, MetricsFamily const& metricsFamily)
{
if (metricsFamily.description_)
stream << "# HELP " << metricsFamily.name_ << ' ' << *metricsFamily.description_ << '\n';
stream << "# TYPE " << metricsFamily.name_ << ' ' << toString(metricsFamily.type()) << '\n';
for (auto const& [labelsString, metric] : metricsFamily.metrics_) {
stream << *metric << '\n';
}
stream << '\n';
return stream;
}
std::string const&
MetricsFamily::name() const
{
return name_;
}
MetricType
MetricsFamily::type() const
{
return type_;
}
template <typename ValueType>
requires std::same_as<ValueType, std::int64_t> || std::same_as<ValueType, double>
MetricBase&
MetricsFamily::getMetricImpl(Labels labels, std::vector<ValueType> const& buckets)
{
auto labelsString = labels.serialize();
auto it = metrics_.find(labelsString);
if (it == metrics_.end()) {
auto metric = metricBuilder_(name(), labelsString, type(), buckets);
auto [it2, success] = metrics_.emplace(std::move(labelsString), std::move(metric));
it = it2;
}
return *it->second;
}
} // namespace util::prometheus

View File

@@ -19,84 +19,25 @@
#pragma once
#include <util/prometheus/Label.h>
#include <util/prometheus/MetricBuilder.h>
#include <fmt/format.h>
#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
namespace util::prometheus {
/**
* @brief Base class for a Prometheus metric containing a name and labels
*/
class MetricBase {
public:
MetricBase(std::string name, std::string labelsString);
MetricBase(MetricBase const&) = delete;
MetricBase(MetricBase&&) = default;
MetricBase&
operator=(MetricBase const&) = delete;
MetricBase&
operator=(MetricBase&&) = default;
virtual ~MetricBase() = default;
/**
* @brief Serialize the metric to a string in Prometheus format
*
* @param s The string to serialize into
*/
void
serialize(std::string& s) const;
/**
* @brief Get the name of the metric
*/
std::string const&
name() const;
/**
* @brief Get the labels of the metric in serialized format, e.g. {name="value",name2="value2"}
*/
std::string const&
labelsString() const;
protected:
/**
* @brief Interface to serialize the value of the metric
*
* @param result The string to serialize into
*/
virtual void
serializeValue(std::string& result) const = 0;
private:
std::string name_;
std::string labelsString_;
};
enum class MetricType { COUNTER_INT, COUNTER_DOUBLE, GAUGE_INT, GAUGE_DOUBLE, HISTOGRAM, SUMMARY };
char const*
toString(MetricType type);
/**
* @brief Class representing a collection of Prometheus metric with the same name and type
*/
class MetricsFamily {
public:
using MetricBuilder = std::function<std::unique_ptr<MetricBase>(std::string, std::string, MetricType)>;
static MetricBuilder defaultMetricBuilder;
static std::unique_ptr<MetricBuilderInterface> defaultMetricBuilder;
MetricsFamily(
std::string name,
std::optional<std::string> description,
MetricType type,
MetricBuilder& builder = defaultMetricBuilder
MetricBuilderInterface& builder = *defaultMetricBuilder
);
MetricsFamily(MetricsFamily const&) = delete;
@@ -110,18 +51,32 @@ public:
* @brief Get the metric with the given labels. If it does not exist, it will be created
*
* @param labels The labels of the metric
* @param buckets The buckets of the histogram. It is ignored for other metric types or if the metric already exists
* @return Reference to the metric
*/
MetricBase&
getMetric(Labels labels);
getMetric(Labels labels, std::vector<std::int64_t> const& buckets = {});
/**
* @brief Serialize all the containing metrics to a string in Prometheus format as one block
* @brief Get the metric with the given labels. If it does not exist, it will be created
*
* @param result The string to serialize into
* @note This overload is only used for histograms with integer buckets
*
* @param labels The labels of the metric
* @param buckets The buckets of the histogram. It is ignored for other metric types or if the metric already exists
* @return Reference to the metric
*/
void
serialize(std::string& result) const;
MetricBase&
getMetric(Labels labels, std::vector<double> const& buckets);
/**
* @brief Serialize the metrics to a string in Prometheus format as one block
*
* @param stream The stream to serialize into
* @param metricsFamily The metrics to serialize
*/
friend OStream&
operator<<(OStream& stream, MetricsFamily const& metricsFamily);
std::string const&
name() const;
@@ -134,7 +89,12 @@ private:
std::optional<std::string> description_;
std::unordered_map<std::string, std::unique_ptr<MetricBase>> metrics_;
MetricType type_;
MetricBuilder& metricBuilder_;
MetricBuilderInterface& metricBuilder_;
template <typename ValueType>
requires std::same_as<ValueType, std::int64_t> || std::same_as<ValueType, double>
MetricBase&
getMetricImpl(Labels labels, std::vector<ValueType> const& buckets);
};
} // namespace util::prometheus

View File

@@ -0,0 +1,41 @@
//------------------------------------------------------------------------------
/*
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>
namespace util::prometheus {
OStream::OStream(bool const compressionEnabled) : compressionEnabled_(compressionEnabled)
{
if (compressionEnabled_) {
stream_.push(boost::iostreams::gzip_compressor{
boost::iostreams::gzip_params{boost::iostreams::gzip::best_compression}});
}
stream_.push(boost::iostreams::back_inserter(buffer_));
}
std::string
OStream::data() &&
{
stream_.reset();
return std::move(buffer_);
}
} // namespace util::prometheus

View File

@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
/*
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 <boost/iostreams/filtering_stream.hpp>
#include <string>
namespace util::prometheus {
/**
* @brief A stream that can optionally compress its data
*/
class OStream {
public:
/**
* @brief Construct a new OStream object
*
* @param compressionEnabled Whether to compress the data
*/
OStream(bool compressionEnabled);
OStream(OStream const&) = delete;
OStream(OStream&&) = delete;
~OStream() = default;
/**
* @brief Write to the stream
*/
template <typename T>
OStream&
operator<<(T const& value)
{
stream_ << value;
return *this;
}
/**
* @brief Get the data from the stream.
*
* @note This resets the stream and clears the buffer. Stream cannot be used after this.
*
* @return The data
*/
std::string
data() &&;
private:
bool compressionEnabled_;
std::string buffer_;
boost::iostreams::filtering_ostream stream_;
};
} // namespace util::prometheus

View File

@@ -20,6 +20,9 @@
#include <util/Assert.h>
#include <util/prometheus/Prometheus.h>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_stream.hpp>
namespace util::prometheus {
namespace {
@@ -67,18 +70,57 @@ PrometheusImpl::gaugeDouble(std::string name, Labels labels, std::optional<std::
return convertBaseTo<GaugeDouble>(metricBase);
}
HistogramInt&
PrometheusImpl::histogramInt(
std::string name,
Labels labels,
std::vector<std::int64_t> const& buckets,
std::optional<std::string> description
)
{
MetricBase& metricBase =
getMetric(std::move(name), std::move(labels), std::move(description), MetricType::HISTOGRAM_INT, buckets);
return convertBaseTo<HistogramInt>(metricBase);
}
HistogramDouble&
PrometheusImpl::histogramDouble(
std::string name,
Labels labels,
std::vector<double> const& buckets,
std::optional<std::string> description
)
{
MetricBase& metricBase =
getMetric(std::move(name), std::move(labels), std::move(description), MetricType::HISTOGRAM_DOUBLE, buckets);
return convertBaseTo<HistogramDouble>(metricBase);
}
std::string
PrometheusImpl::collectMetrics()
{
std::string result;
if (!isEnabled())
return result;
return {};
OStream stream{compressReplyEnabled()};
for (auto const& [name, family] : metrics_) {
family.serialize(result);
stream << family;
}
return result;
return std::move(stream).data();
}
MetricsFamily&
PrometheusImpl::getMetricsFamily(std::string name, std::optional<std::string> description, MetricType type)
{
auto it = metrics_.find(name);
if (it == metrics_.end()) {
auto nameCopy = name;
it = metrics_.emplace(std::move(nameCopy), MetricsFamily(std::move(name), std::move(description), type)).first;
} else if (it->second.type() != type) {
throw std::runtime_error("Metrics of different type can't have the same name: " + name);
}
return it->second;
}
MetricBase&
@@ -89,14 +131,23 @@ PrometheusImpl::getMetric(
MetricType const type
)
{
auto it = metrics_.find(name);
if (it == metrics_.end()) {
auto nameCopy = name;
it = metrics_.emplace(std::move(nameCopy), MetricsFamily(std::move(name), std::move(description), type)).first;
} else if (it->second.type() != type) {
throw std::runtime_error("Metrics of different type can't have the same name: " + name);
}
return it->second.getMetric(std::move(labels));
auto& metricFamily = getMetricsFamily(std::move(name), std::move(description), type);
return metricFamily.getMetric(std::move(labels));
}
template <typename ValueType>
requires std::same_as<ValueType, std::int64_t> || std::same_as<ValueType, double>
MetricBase&
PrometheusImpl::getMetric(
std::string name,
Labels labels,
std::optional<std::string> description,
MetricType type,
std::vector<ValueType> const& buckets
)
{
auto& metricFamily = getMetricsFamily(std::move(name), std::move(description), type);
return metricFamily.getMetric(std::move(labels), buckets);
}
} // namespace util::prometheus
@@ -104,8 +155,10 @@ PrometheusImpl::getMetric(
void
PrometheusService::init(util::Config const& config)
{
bool const enabled = config.valueOr("prometheus_enabled", true);
instance_ = std::make_unique<util::prometheus::PrometheusImpl>(enabled);
bool const enabled = config.valueOr("prometheus.enabled", true);
bool const compressReply = config.valueOr("prometheus.compress_reply", true);
instance_ = std::make_unique<util::prometheus::PrometheusImpl>(enabled, compressReply);
}
util::prometheus::CounterInt&
@@ -140,6 +193,28 @@ PrometheusService::gaugeDouble(
return instance().gaugeDouble(std::move(name), std::move(labels), std::move(description));
}
util::prometheus::HistogramInt&
PrometheusService::histogramInt(
std::string name,
util::prometheus::Labels labels,
std::vector<std::int64_t> const& buckets,
std::optional<std::string> description
)
{
return instance().histogramInt(std::move(name), std::move(labels), buckets, std::move(description));
}
util::prometheus::HistogramDouble&
PrometheusService::histogramDouble(
std::string name,
util::prometheus::Labels labels,
std::vector<double> const& buckets,
std::optional<std::string> description
)
{
return instance().histogramDouble(std::move(name), std::move(labels), buckets, std::move(description));
}
std::string
PrometheusService::collectMetrics()
{
@@ -152,6 +227,12 @@ PrometheusService::isEnabled()
return instance().isEnabled();
}
bool
PrometheusService::compressReplyEnabled()
{
return instance().compressReplyEnabled();
}
void
PrometheusService::replaceInstance(std::unique_ptr<util::prometheus::PrometheusInterface> instance)
{

View File

@@ -22,6 +22,8 @@
#include <util/config/Config.h>
#include <util/prometheus/Counter.h>
#include <util/prometheus/Gauge.h>
#include <util/prometheus/Histogram.h>
#include <util/prometheus/MetricsFamily.h>
namespace util::prometheus {
@@ -32,18 +34,20 @@ public:
*
* @param isEnabled Whether prometheus is enabled
*/
PrometheusInterface(bool isEnabled) : isEnabled_(isEnabled)
PrometheusInterface(bool isEnabled, bool compressReply)
: isEnabled_(isEnabled), compressReplyEnabled_(compressReply)
{
}
virtual ~PrometheusInterface() = default;
/**
* @brief Get a integer based counter metric. It will be created if it doesn't exist
* @brief Get an integer based counter metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
* @param description The description of the metric
* @return CounterDouble& The reference to the counter object
*/
virtual CounterInt&
counterInt(std::string name, Labels labels, std::optional<std::string> description = std::nullopt) = 0;
@@ -54,16 +58,18 @@ public:
* @param name The name of the metric
* @param labels The labels of the metric
* @param description The description of the metric
* @return The reference to the counter object
*/
virtual CounterDouble&
counterDouble(std::string name, Labels labels, std::optional<std::string> description = std::nullopt) = 0;
/**
* @brief Get a integer based gauge metric. It will be created if it doesn't exist
* @brief Get an integer based gauge metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
* @param description The description of the metric
* @return The reference to the gauge object
*/
virtual GaugeInt&
gaugeInt(std::string name, Labels labels, std::optional<std::string> description = std::nullopt) = 0;
@@ -74,10 +80,45 @@ public:
* @param name The name of the metric
* @param labels The labels of the metric
* @param description The description of the metric
* @return The reference to the gauge object
*/
virtual GaugeDouble&
gaugeDouble(std::string name, Labels labels, std::optional<std::string> description = std::nullopt) = 0;
/**
* @brief Get an integer based histogram metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
* @param buckets The buckets of the metric
* @param description The description of the metric
* @return The reference to the histogram object
*/
virtual HistogramInt&
histogramInt(
std::string name,
Labels labels,
std::vector<std::int64_t> const& buckets,
std::optional<std::string> description = std::nullopt
) = 0;
/**
* @brief Get a double based histogram metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
* @param buckets The buckets of the metric
* @param description The description of the metric
* @return The reference to the histogram object
*/
virtual HistogramDouble&
histogramDouble(
std::string name,
Labels labels,
std::vector<double> const& buckets,
std::optional<std::string> description = std::nullopt
) = 0;
/**
* @brief Collect all metrics and return them as a string in Prometheus format
*
@@ -97,8 +138,20 @@ public:
return isEnabled_;
}
/**
* @brief Whether to compress the reply
*
* @return true if the reply should be compressed
*/
bool
compressReplyEnabled() const
{
return compressReplyEnabled_;
}
private:
bool isEnabled_;
bool compressReplyEnabled_;
};
/**
@@ -122,13 +175,43 @@ public:
GaugeDouble&
gaugeDouble(std::string name, Labels labels, std::optional<std::string> description) override;
HistogramInt&
histogramInt(
std::string name,
Labels labels,
std::vector<std::int64_t> const& buckets,
std::optional<std::string> description = std::nullopt
) override;
HistogramDouble&
histogramDouble(
std::string name,
Labels labels,
std::vector<double> const& buckets,
std::optional<std::string> description = std::nullopt
) override;
std::string
collectMetrics() override;
private:
MetricsFamily&
getMetricsFamily(std::string name, std::optional<std::string> description, MetricType type);
MetricBase&
getMetric(std::string name, Labels labels, std::optional<std::string> description, MetricType type);
template <typename ValueType>
requires std::same_as<ValueType, std::int64_t> || std::same_as<ValueType, double>
MetricBase&
getMetric(
std::string name,
Labels labels,
std::optional<std::string> description,
MetricType type,
std::vector<ValueType> const& buckets
);
std::unordered_map<std::string, MetricsFamily> metrics_;
};
@@ -147,7 +230,7 @@ public:
void static init(util::Config const& config = util::Config{});
/**
* @brief Get a integer based counter metric. It will be created if it doesn't exist
* @brief Get an integer based counter metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
@@ -175,7 +258,7 @@ public:
);
/**
* @brief Get a integer based gauge metric. It will be created if it doesn't exist
* @brief Get an integer based gauge metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
@@ -198,6 +281,40 @@ public:
std::optional<std::string> description = std::nullopt
);
/**
* @brief Get an integer based histogram metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
* @param buckets The buckets of the metric
* @param description The description of the metric
* @return The reference to the histogram object
*/
static util::prometheus::HistogramInt&
histogramInt(
std::string name,
util::prometheus::Labels labels,
std::vector<std::int64_t> const& buckets,
std::optional<std::string> description = std::nullopt
);
/**
* @brief Get a double based histogram metric. It will be created if it doesn't exist
*
* @param name The name of the metric
* @param labels The labels of the metric
* @param buckets The buckets of the metric
* @param description The description of the metric
* @return The reference to the histogram object
*/
static util::prometheus::HistogramDouble&
histogramDouble(
std::string name,
util::prometheus::Labels labels,
std::vector<double> const& buckets,
std::optional<std::string> description = std::nullopt
);
/**
* @brief Collect all metrics and return them as a string in Prometheus format
*
@@ -214,6 +331,14 @@ public:
static bool
isEnabled();
/**
* @brief Whether to compress the reply
*
* @return true if the reply should be compressed
*/
static bool
compressReplyEnabled();
/**
* @brief Replace the prometheus object stored in the singleton
*

View File

@@ -51,7 +51,9 @@ protected:
template <SomeCounterImpl ImplType>
struct Model : Concept {
Model(ImplType impl) : impl_(std::forward<ImplType>(impl))
template <SomeCounterImpl SomeImplType>
requires std::same_as<ImplType, SomeImplType>
Model(SomeImplType&& impl) : impl_(std::forward<SomeImplType>(impl))
{
}

View File

@@ -20,15 +20,13 @@
#pragma once
#include <util/Assert.h>
#include <util/Atomic.h>
#include <atomic>
#include <concepts>
namespace util::prometheus::detail {
template <typename T>
concept SomeNumberType = std::is_arithmetic_v<T> && !std::is_same_v<T, bool> && !std::is_const_v<T>;
template <typename T>
concept SomeCounterImpl = requires(T a) {
typename std::remove_cvref_t<T>::ValueType;
@@ -53,50 +51,33 @@ public:
CounterImpl(CounterImpl const&) = delete;
// Move constructor should be used only used during initialization
CounterImpl(CounterImpl&& other)
{
ASSERT(other.value_ == 0, "Move constructor should only be used during initialization");
value_ = other.value_.exchange(0);
}
CounterImpl(CounterImpl&& other) = default;
CounterImpl&
operator=(CounterImpl const&) = delete;
CounterImpl&
operator=(CounterImpl&&) = delete;
operator=(CounterImpl&&) = default;
void
add(ValueType const value)
{
if constexpr (std::is_integral_v<ValueType>) {
value_.fetch_add(value);
} else {
#if __cpp_lib_atomic_float >= 201711L
value_.fetch_add(value);
#else
// Workaround for atomic float not being supported by the standard library
// cimpares_exchange_weak returns false if the value is not exchanged and updates the current value
auto current = value_.load();
while (!value_.compare_exchange_weak(current, current + value)) {
}
#endif
}
value_->add(value);
}
void
set(ValueType const value)
{
value_ = value;
value_->set(value);
}
ValueType
value() const
{
return value_;
return value_->value();
}
private:
std::atomic<ValueType> value_{0};
AtomicPtr<ValueType> value_ = std::make_unique<Atomic<ValueType>>(0);
};
} // namespace util::prometheus::detail

View File

@@ -0,0 +1,134 @@
//------------------------------------------------------------------------------
/*
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.h>
#include <util/Concepts.h>
#include <util/prometheus/OStream.h>
#include <mutex>
namespace util::prometheus::detail {
template <typename T>
concept SomeHistogramImpl = requires(T t) {
typename std::remove_cvref_t<T>::ValueType;
SomeNumberType<typename std::remove_cvref_t<T>::ValueType>;
{
t.observe(typename std::remove_cvref_t<T>::ValueType{1})
} -> std::same_as<void>;
{
t.setBuckets(std::vector<typename std::remove_cvref_t<T>::ValueType>{})
} -> std::same_as<void>;
{
t.serializeValue(std::string{}, std::string{}, std::declval<OStream&>())
} -> std::same_as<void>;
};
template <SomeNumberType NumberType>
class HistogramImpl {
public:
using ValueType = NumberType;
HistogramImpl() = default;
HistogramImpl(HistogramImpl const&) = delete;
HistogramImpl(HistogramImpl&&) = default;
HistogramImpl&
operator=(HistogramImpl const&) = delete;
HistogramImpl&
operator=(HistogramImpl&&) = default;
void
setBuckets(std::vector<ValueType> const& bounds)
{
std::scoped_lock const lock{*mutex_};
ASSERT(buckets_.empty(), "Buckets can be set only once.");
buckets_.reserve(bounds.size());
for (auto const& bound : bounds) {
buckets_.emplace_back(bound);
}
}
void
observe(ValueType const value)
{
auto const bucket =
std::lower_bound(buckets_.begin(), buckets_.end(), value, [](Bucket const& bucket, ValueType const& value) {
return bucket.upperBound < value;
});
std::scoped_lock const lock{*mutex_};
if (bucket != buckets_.end()) {
++bucket->count;
} else {
++lastBucket_.count;
}
sum_ += value;
}
void
serializeValue(std::string const& name, std::string labelsString, OStream& stream) const
{
if (labelsString.empty()) {
labelsString = "{";
} else {
ASSERT(
labelsString.front() == '{' && labelsString.back() == '}',
"Labels must be in Prometheus serialized format."
);
labelsString.back() = ',';
}
std::scoped_lock const lock{*mutex_};
std::uint64_t cumulativeCount = 0;
for (auto const& bucket : buckets_) {
cumulativeCount += bucket.count;
stream << name << "_bucket" << labelsString << "le=\"" << bucket.upperBound << "\"} " << cumulativeCount
<< '\n';
}
cumulativeCount += lastBucket_.count;
stream << name << "_bucket" << labelsString << "le=\"+Inf\"} " << cumulativeCount << '\n';
if (labelsString.size() == 1) {
labelsString = "";
} else {
labelsString.back() = '}';
}
stream << name << "_sum" << labelsString << " " << sum_ << '\n';
stream << name << "_count" << labelsString << " " << cumulativeCount << '\n';
}
private:
struct Bucket {
Bucket(ValueType upperBound) : upperBound(upperBound)
{
}
ValueType upperBound;
std::uint64_t count = 0;
};
std::vector<Bucket> buckets_;
Bucket lastBucket_{std::numeric_limits<ValueType>::max()};
ValueType sum_ = 0;
mutable std::unique_ptr<std::mutex> mutex_ = std::make_unique<std::mutex>();
};
} // namespace util::prometheus::detail

View File

@@ -48,6 +48,7 @@ struct BackendCountersTest : WithPrometheus {
}
BackendCounters::PtrType const counters = BackendCounters::make();
std::chrono::steady_clock::time_point startTime{};
};
TEST_F(BackendCountersTest, EmptyByDefault)
@@ -68,8 +69,9 @@ TEST_F(BackendCountersTest, RegisterTooBusy)
TEST_F(BackendCountersTest, RegisterWriteSync)
{
counters->registerWriteSync();
counters->registerWriteSync();
std::chrono::steady_clock::time_point const startTime{};
counters->registerWriteSync(startTime);
counters->registerWriteSync(startTime);
auto expectedReport = emptyReport();
expectedReport["write_sync"] = 2;
@@ -102,8 +104,8 @@ TEST_F(BackendCountersTest, RegisterWriteFinished)
counters->registerWriteStarted();
counters->registerWriteStarted();
counters->registerWriteStarted();
counters->registerWriteFinished();
counters->registerWriteFinished();
counters->registerWriteFinished(startTime);
counters->registerWriteFinished(startTime);
auto expectedReport = emptyReport();
expectedReport["write_async_pending"] = 1;
@@ -136,8 +138,8 @@ TEST_F(BackendCountersTest, RegisterReadFinished)
counters->registerReadStarted();
counters->registerReadStarted();
counters->registerReadStarted();
counters->registerReadFinished();
counters->registerReadFinished();
counters->registerReadFinished(startTime);
counters->registerReadFinished(startTime);
auto expectedReport = emptyReport();
expectedReport["read_async_pending"] = 1;
@@ -151,7 +153,7 @@ TEST_F(BackendCountersTest, RegisterReadStartedFinishedWithCounters)
static constexpr auto OPERATIONS_COMPLETED = 4u;
counters->registerReadStarted(OPERATIONS_STARTED);
counters->registerReadFinished(OPERATIONS_COMPLETED);
counters->registerReadFinished(startTime, OPERATIONS_COMPLETED);
auto expectedReport = emptyReport();
expectedReport["read_async_pending"] = OPERATIONS_STARTED - OPERATIONS_COMPLETED;
@@ -177,7 +179,7 @@ TEST_F(BackendCountersTest, RegisterReadError)
counters->registerReadStarted(OPERATIONS_STARTED);
counters->registerReadError(OPERATIONS_ERROR);
counters->registerReadFinished(OPERATIONS_COMPLETED);
counters->registerReadFinished(startTime, OPERATIONS_COMPLETED);
auto expectedReport = emptyReport();
expectedReport["read_async_pending"] = OPERATIONS_STARTED - OPERATIONS_COMPLETED - OPERATIONS_ERROR;
@@ -200,8 +202,11 @@ TEST_F(BackendCountersMockPrometheusTest, registerTooBusy)
TEST_F(BackendCountersMockPrometheusTest, registerWriteSync)
{
auto& counter = makeMock<CounterInt>("backend_operations_total_number", "{operation=\"write_sync\"}");
auto& histogram = makeMock<HistogramInt>("backend_duration_milliseconds_histogram", "{operation=\"write\"}");
EXPECT_CALL(counter, add(1));
counters->registerWriteSync();
EXPECT_CALL(histogram, observe(testing::_));
std::chrono::steady_clock::time_point const startTime{};
counters->registerWriteSync(startTime);
}
TEST_F(BackendCountersMockPrometheusTest, registerWriteSyncRetry)
@@ -225,10 +230,13 @@ TEST_F(BackendCountersMockPrometheusTest, registerWriteFinished)
makeMock<GaugeInt>("backend_operations_current_number", "{operation=\"write_async\",status=\"pending\"}");
auto& completedCounter =
makeMock<CounterInt>("backend_operations_total_number", "{operation=\"write_async\",status=\"completed\"}");
auto& histogram = makeMock<HistogramInt>("backend_duration_milliseconds_histogram", "{operation=\"write\"}");
EXPECT_CALL(pendingCounter, value()).WillOnce(testing::Return(1));
EXPECT_CALL(pendingCounter, add(-1));
EXPECT_CALL(completedCounter, add(1));
counters->registerWriteFinished();
EXPECT_CALL(histogram, observe(testing::_));
std::chrono::steady_clock::time_point const startTime{};
counters->registerWriteFinished(startTime);
}
TEST_F(BackendCountersMockPrometheusTest, registerWriteRetry)
@@ -253,10 +261,13 @@ TEST_F(BackendCountersMockPrometheusTest, registerReadFinished)
makeMock<GaugeInt>("backend_operations_current_number", "{operation=\"read_async\",status=\"pending\"}");
auto& completedCounter =
makeMock<CounterInt>("backend_operations_total_number", "{operation=\"read_async\",status=\"completed\"}");
EXPECT_CALL(pendingCounter, value()).WillOnce(testing::Return(1));
EXPECT_CALL(pendingCounter, add(-1));
EXPECT_CALL(completedCounter, add(1));
counters->registerReadFinished();
auto& histogram = makeMock<HistogramInt>("backend_duration_milliseconds_histogram", "{operation=\"read\"}");
EXPECT_CALL(pendingCounter, value()).WillOnce(testing::Return(2));
EXPECT_CALL(pendingCounter, add(-2));
EXPECT_CALL(completedCounter, add(2));
EXPECT_CALL(histogram, observe(testing::_)).Times(2);
std::chrono::steady_clock::time_point const startTime{};
counters->registerReadFinished(startTime, 2);
}
TEST_F(BackendCountersMockPrometheusTest, registerReadRetry)

View File

@@ -40,10 +40,10 @@ protected:
}
MOCK_METHOD(void, registerTooBusy, (), ());
MOCK_METHOD(void, registerWriteSync, (), ());
MOCK_METHOD(void, registerWriteSync, (std::chrono::steady_clock::time_point), ());
MOCK_METHOD(void, registerWriteSyncRetry, (), ());
MOCK_METHOD(void, registerWriteStarted, (), ());
MOCK_METHOD(void, registerWriteFinished, (), ());
MOCK_METHOD(void, registerWriteFinished, (std::chrono::steady_clock::time_point), ());
MOCK_METHOD(void, registerWriteRetry, (), ());
void
@@ -54,11 +54,11 @@ protected:
MOCK_METHOD(void, registerReadStartedImpl, (std::uint64_t), ());
void
registerReadFinished(std::uint64_t count = 1)
registerReadFinished(std::chrono::steady_clock::time_point startTime, std::uint64_t count = 1)
{
registerReadFinishedImpl(count);
registerReadFinishedImpl(startTime, count);
}
MOCK_METHOD(void, registerReadFinishedImpl, (std::uint64_t), ());
MOCK_METHOD(void, registerReadFinishedImpl, (std::chrono::steady_clock::time_point, std::uint64_t), ());
void
registerReadRetry(std::uint64_t count = 1)
@@ -110,7 +110,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, ReadOneInCoroutineSuccessful)
EXPECT_CALL(handle, asyncExecute(A<FakeStatement const&>(), A<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
EXPECT_CALL(*counters, registerReadStartedImpl(1));
EXPECT_CALL(*counters, registerReadFinishedImpl(1));
EXPECT_CALL(*counters, registerReadFinishedImpl(testing::_, 1));
runSpawn([&strat](boost::asio::yield_context yield) {
auto statement = FakeStatement{};
@@ -175,7 +175,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, ReadBatchInCoroutineSuccessful)
)
.Times(1);
EXPECT_CALL(*counters, registerReadStartedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadFinishedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadFinishedImpl(testing::_, NUM_STATEMENTS));
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(NUM_STATEMENTS);
@@ -249,7 +249,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, ReadBatchInCoroutineMarksBusyIfReq
)
.Times(1);
EXPECT_CALL(*counters, registerReadStartedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadFinishedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadFinishedImpl(testing::_, NUM_STATEMENTS));
runSpawn([&strat](boost::asio::yield_context yield) {
EXPECT_FALSE(strat.isTooBusy()); // 2 was the limit, 0 atm
@@ -277,7 +277,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, ReadEachInCoroutineSuccessful)
)
.Times(NUM_STATEMENTS); // once per statement
EXPECT_CALL(*counters, registerReadStartedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadFinishedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadFinishedImpl(testing::_, NUM_STATEMENTS));
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(NUM_STATEMENTS);
@@ -311,7 +311,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, ReadEachInCoroutineThrowsOnFailure
.Times(NUM_STATEMENTS); // once per statement
EXPECT_CALL(*counters, registerReadStartedImpl(NUM_STATEMENTS));
EXPECT_CALL(*counters, registerReadErrorImpl(1));
EXPECT_CALL(*counters, registerReadFinishedImpl(2));
EXPECT_CALL(*counters, registerReadFinishedImpl(testing::_, 2));
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(NUM_STATEMENTS);
@@ -326,7 +326,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, WriteSyncFirstTrySuccessful)
ON_CALL(handle, execute(A<FakeStatement const&>())).WillByDefault([](auto const&) { return FakeResultOrError{}; });
EXPECT_CALL(handle,
execute(A<FakeStatement const&>())).Times(1); // first one will succeed
EXPECT_CALL(*counters, registerWriteSync());
EXPECT_CALL(*counters, registerWriteSync(testing::_));
EXPECT_TRUE(strat.writeSync({}));
}
@@ -344,7 +344,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, WriteSyncRetrySuccessful)
EXPECT_CALL(handle,
execute(A<FakeStatement const&>())).Times(2); // first one will fail, second will succeed
EXPECT_CALL(*counters, registerWriteSyncRetry());
EXPECT_CALL(*counters, registerWriteSync());
EXPECT_CALL(*counters, registerWriteSync(testing::_));
EXPECT_TRUE(strat.writeSync({}));
}
@@ -376,7 +376,7 @@ TEST_F(BackendCassandraExecutionStrategyTest, WriteMultipleAndCallSyncSucceeds)
)
.Times(totalRequests); // one per write call
EXPECT_CALL(*counters, registerWriteStarted()).Times(totalRequests);
EXPECT_CALL(*counters, registerWriteFinished()).Times(totalRequests);
EXPECT_CALL(*counters, registerWriteFinished(testing::_)).Times(totalRequests);
auto makeStatements = [] { return std::vector<FakeStatement>(16); };
for (auto i = 0u; i < totalRequests; ++i)

View 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);
}

View File

@@ -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

View File

@@ -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)

View 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"
);
}

View File

@@ -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);
}

View 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>{}); }, "");
}

View File

@@ -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));

View 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);
}

View File

@@ -583,12 +583,12 @@ TEST_F(WebServerPrometheusTest, rejectedIfPrometheusIsDisabled)
{
static auto constexpr JSONServerConfigWithDisabledPrometheus = R"JSON(
{
"server":{
"server": {
"ip": "0.0.0.0",
"port": 8888,
"admin_password": "secret"
},
"prometheus_enabled": false
"prometheus": { "enabled": false }
}
)JSON";