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