mirror of
				https://github.com/XRPLF/clio.git
				synced 2025-11-04 11:55:51 +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