metrics_exporter_prometheus/
common.rs

1use std::collections::HashMap;
2
3use crate::{distribution::Distribution, PrometheusRecorder};
4
5use crate::formatting::sanitize_metric_name;
6use indexmap::IndexMap;
7use metrics::SetRecorderError;
8use thiserror::Error;
9
10/// Matches a metric name in a specific way.
11///
12/// Used for specifying overrides for buckets, allowing a default set of histogram buckets to be
13/// specified while adjusting the buckets that get used for specific metrics.
14#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub enum Matcher {
16    /// Matches the entire metric name.
17    Full(String),
18    /// Matches the beginning of the metric name.
19    Prefix(String),
20    /// Matches the end of the metric name.
21    Suffix(String),
22}
23
24impl Matcher {
25    /// Checks if the given key matches this matcher.
26    pub fn matches(&self, key: &str) -> bool {
27        match self {
28            Matcher::Prefix(prefix) => key.starts_with(prefix),
29            Matcher::Suffix(suffix) => key.ends_with(suffix),
30            Matcher::Full(full) => key == full,
31        }
32    }
33
34    /// Creates a sanitized version of this matcher.
35    pub(crate) fn sanitized(self) -> Matcher {
36        match self {
37            Matcher::Prefix(prefix) => Matcher::Prefix(sanitize_metric_name(prefix.as_str())),
38            Matcher::Suffix(suffix) => Matcher::Suffix(sanitize_metric_name(suffix.as_str())),
39            Matcher::Full(full) => Matcher::Full(sanitize_metric_name(full.as_str())),
40        }
41    }
42}
43
44/// Errors that could occur while building or installing a Prometheus recorder/exporter.
45#[derive(Debug, Error)]
46pub enum BuildError {
47    /// There was an issue when creating the necessary Tokio runtime to launch the exporter.
48    #[error("failed to create Tokio runtime for exporter: {0}")]
49    FailedToCreateRuntime(String),
50
51    /// There was an issue when creating the HTTP listener.
52    #[error("failed to create HTTP listener: {0}")]
53    FailedToCreateHTTPListener(String),
54
55    /// Installing the recorder did not succeed.
56    #[error("failed to install exporter as global recorder: {0}")]
57    FailedToSetGlobalRecorder(#[from] SetRecorderError<PrometheusRecorder>),
58
59    /// The given address could not be parsed successfully as an IP address/subnet.
60    #[error("failed to parse address as a valid IP address/subnet: {0}")]
61    InvalidAllowlistAddress(String),
62
63    /// The given push gateway endpoint is not a valid URI.
64    #[error("push gateway endpoint is not valid: {0}")]
65    InvalidPushGatewayEndpoint(String),
66
67    /// No exporter configuration was present.
68    ///
69    /// This generally only occurs when HTTP listener support is disabled, but no push gateway
70    /// configuration was give to the builder.
71    #[error("attempted to build exporter with no exporters enabled; did you disable default features and forget to enable either the `http-listener` or `push-gateway` features?")]
72    MissingExporterConfiguration,
73
74    /// Bucket bounds or quantiles were empty.
75    #[error("bucket bounds/quantiles cannot be empty")]
76    EmptyBucketsOrQuantiles,
77
78    /// Bucket duration cannot be zero.
79    #[error("bucket durations cannot be set to zero")]
80    ZeroBucketDuration,
81}
82
83/// Represents a set of labels as structured key-value pairs
84#[derive(Debug, Clone, PartialEq, Eq, Hash)]
85pub struct LabelSet {
86    pub(crate) labels: Vec<(String, String)>,
87}
88
89impl LabelSet {
90    /// Creates a new `LabelSet` from the given key and a set of global labels.
91    pub fn from_key_and_global(
92        key: &metrics::Key,
93        global_labels: &IndexMap<String, String>,
94    ) -> Self {
95        let mut labels = global_labels.clone();
96        key.labels().for_each(|label| {
97            labels.insert(label.key().to_string(), label.value().to_string());
98        });
99        Self { labels: labels.into_iter().collect() }
100    }
101
102    /// Returns `true` if the label set is empty.
103    pub fn is_empty(&self) -> bool {
104        self.labels.is_empty()
105    }
106
107    /// Returns an iterator that yields the labels in a sanitized and concatenated format.
108    pub fn to_strings(&self) -> impl Iterator<Item = String> + '_ {
109        self.labels.iter().map(|(k, v)| {
110            format!(
111                "{}=\"{}\"",
112                crate::formatting::sanitize_label_key(k),
113                crate::formatting::sanitize_label_value(v)
114            )
115        })
116    }
117}
118
119#[derive(Debug)]
120pub struct Snapshot {
121    pub counters: HashMap<String, HashMap<LabelSet, u64>>,
122    pub gauges: HashMap<String, HashMap<LabelSet, f64>>,
123    pub distributions: HashMap<String, IndexMap<LabelSet, Distribution>>,
124}