1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
use crate::guards::DeferredAddWithLabels;
use prometheus::{
    register_histogram_vec, register_int_counter_vec, register_int_gauge_vec, HistogramTimer,
    HistogramVec, IntCounterVec, IntGaugeVec,
};
use std::marker::PhantomData;

/// A sequence of values for Prometheus labels
pub type LabelValues<'a> = Vec<&'a str>;

/// The `Labels` trait applies to values intended to generate Prometheus labels for a metric.
///
/// A metric in Prometheus can include any number of labels. Each label has a fixed name,
/// and when events are emitted for the metric, those events must include values for each
/// of the labels. Using labels makes it possible to easily see a metric in aggregate (i.e.,
/// to see totals regardless of label values), or to query specific kinds of events by
/// filtering label values.
///
/// Thus, for example, rather than having a separate metric for each kind of error that
/// arises, we can produce one metric with an "err" label, whose value will reflect which
/// error has occurred. That simplifies the top-level list of metrics, and makes it easier
/// to build queries to aggregate specific kinds of error events.
///
/// This trait adds some extra guard rails on top of the prometheus-rs crate, so that when
/// we emit a labeled metric we can use a custom type to represent the labels, rather than
/// working directly with slices of string slices. When defining a labeled metric, you should
/// also define a new type representing its labels that implements the `Labels` trait.
/// Then, when emitting events for the metric, labels are passed in using this custom type,
/// which rules out several kinds of bugs (like missing or incorrectly ordered label values).
///
/// You can define labeled metrics using types like [`IntCounterWithLabels`], which are
/// parameterized by a type that implements `Labels`.
///
/// [`IntCounterWithLabels`]: struct.IntCounterWithLabels.html
pub trait Labels {
    /// The names of the labels that will be defined for the corresponding metric.
    fn label_names() -> Vec<&'static str>;

    /// Labels values to seed the metric with initially.
    ///
    /// Since Prometheus doesn't know the possible values a label will take on, when we set
    /// up a labeled metric by default no values will appear until events are emitted. But
    /// for discoverability, it's helpful to initialize a labeled metric with some possible
    /// label values (at count 0) even if no events for the metric have occurred.
    ///
    /// The label values provided by this function are used to pre-populate the metric at
    /// count 0. **The values do _not_ need to be exhaustive**; it's fine for events to emit
    /// label values that are not included here.
    fn possible_label_values() -> Vec<LabelValues<'static>>;

    /// The actual label values to provide when emitting an event to Prometheus.
    ///
    /// The sequence of values should correspond to the names provided in `label_names`,
    /// in order.
    fn label_values(&self) -> LabelValues;
}

/// A Prometheus integer counter metric, with labels described by the type `L`.
///
/// The type `L` must implement the [`Labels`] trait; see the documentation for that trait
/// for an overview of Prometheus metric labels.
///
/// [`Labels`]: trait.Labels.html
pub struct IntCounterWithLabels<L: Labels> {
    metric: IntCounterVec,
    _labels: PhantomData<L>,
}

impl<L: Labels> IntCounterWithLabels<L> {
    /// Construct and immediately register a new `IntCounterWithLabels` instance.
    pub fn register_new(name: &str, help: &str) -> IntCounterWithLabels<L> {
        let metric = register_int_counter_vec!(name, help, &L::label_names()).unwrap();

        for vals in L::possible_label_values() {
            metric.with_label_values(&vals).inc_by(0);
        }

        Self {
            metric,
            _labels: PhantomData,
        }
    }

    /// Increment the metric by `1`, using the provided `labels` for the event.
    pub fn inc(&self, labels: &L) {
        self.metric.with_label_values(&labels.label_values()).inc();
    }

    /// Increment the metric by `v`, using the provided `labels` for the event.
    pub fn add(&self, v: u64, labels: &L) {
        self.metric
            .with_label_values(&labels.label_values())
            .inc_by(v);
    }

    /// Creates a guard value that will increment the metric by `1`, using the provided `labels`,
    /// once dropped.
    ///
    /// Prior to dropping, the labels can be altered using [`DeferredAddWithLabels::with_labels`].
    #[must_use]
    pub fn deferred_inc<'a>(&'a self, labels: L) -> DeferredAddWithLabels<'a, L> {
        DeferredAddWithLabels::new(self, 1, labels)
    }

    /// Creates a guard value that will increment the metric by `v`, using the provided `labels`,
    /// once dropped.
    ///
    /// Prior to dropping, the labels can be altered using [`DeferredAddWithLabels::with_labels`].
    #[must_use]
    pub fn deferred_add<'a>(&'a self, v: u64, labels: L) -> DeferredAddWithLabels<'a, L> {
        DeferredAddWithLabels::new(self, v, labels)
    }
}

/// A Prometheus integer gauge metric, with labels described by the type `L`.
///
/// The type `L` must implement the [`Labels`] trait; see the documentation for that trait
/// for an overview of Prometheus metric labels.
///
/// [`Labels`]: trait.Labels.html
pub struct IntGaugeWithLabels<L: Labels> {
    metric: IntGaugeVec,
    _labels: PhantomData<L>,
}

impl<L: Labels> IntGaugeWithLabels<L> {
    /// Construct and immediately register a new `IntGaugeWithLabels` instance.
    pub fn register_new(name: &str, help: &str) -> IntGaugeWithLabels<L> {
        let metric = register_int_gauge_vec!(name, help, &L::label_names()).unwrap();

        // Note: for gauges, unlike counters, we don't need to -- and should not! -- prepopulate
        // the metric with the possible labels. Unlike counters, which are only updated when an
        // event occurs, gauges _always_ have a value. Moreover, we cannot make assumptions about
        // an initial gauge value (unlike an initial 0 value for counters).

        Self {
            metric,
            _labels: PhantomData,
        }
    }

    /// Set the value of the gauge with the provided `labels`.
    pub fn set(&self, labels: &L, value: i64) {
        self.metric
            .with_label_values(&labels.label_values())
            .set(value);
    }

    /// Add `value` to the gauge with the provided `labels`.
    pub fn add(&self, labels: &L, value: i64) {
        self.metric
            .with_label_values(&labels.label_values())
            .add(value);
    }

    /// Subtract `value` from the gauge with the provided `labels`.
    pub fn sub(&self, labels: &L, value: i64) {
        self.metric
            .with_label_values(&labels.label_values())
            .sub(value);
    }

    /// Increment the gauge by `1`, using the provided `labels` for the event.
    pub fn inc(&self, labels: &L) {
        self.metric.with_label_values(&labels.label_values()).inc();
    }

    /// Decrement the gauge by `1`, using the provided `labels` for the event.
    pub fn dec(&self, labels: &L) {
        self.metric.with_label_values(&labels.label_values()).dec();
    }
}

/// A Prometheus histogram metric, with labels described by the type `L`.
///
/// The type `L` must implement the [`Labels`] trait; see the documentation for that trait
/// for an overview of Prometheus metric labels.
///
/// [`Labels`]: trait.Labels.html
pub struct HistogramWithLabels<L: Labels> {
    metric: HistogramVec,
    _labels: PhantomData<L>,
}

impl<L: Labels> HistogramWithLabels<L> {
    /// Construct and immediately register a new `HistogramWithLabels` instance.
    pub fn register_new(name: &str, help: &str) -> Self {
        let metric = register_histogram_vec!(name, help, &L::label_names()).unwrap();

        // Note: for histograms, like gauges, we don't need to -- and should not! -- prepopulate
        // the metric with the possible labels.

        Self {
            metric,
            _labels: PhantomData,
        }
    }

    /// Construct and immediately register a new `HistogramWithLabels` instance.
    ///
    /// This will use the provided `buckets` when registering the underlying [`HistogramVec`].
    pub fn register_new_with_buckets(name: &str, help: &str, buckets: Vec<f64>) -> Self {
        let metric = register_histogram_vec!(name, help, &L::label_names(), buckets).unwrap();

        // Note: for histograms, like gauges, we don't need to -- and should not! -- prepopulate
        // the metric with the possible labels.

        Self {
            metric,
            _labels: PhantomData,
        }
    }

    /// Add a single observation to the histogram with the provided `labels`.
    pub fn observe(&self, labels: &L, value: f64) {
        self.metric
            .with_label_values(&labels.label_values())
            .observe(value);
    }

    /// Return a [`HistogramTimer`] to track a duration, using the provided `labels`.
    pub fn start_timer(&self, labels: &L) -> HistogramTimer {
        self.metric
            .with_label_values(&labels.label_values())
            .start_timer()
    }

    /// Observe execution time of a closure, in seconds.
    pub fn observe_closure_duration<F, T>(&self, labels: &L, f: F) -> T
    where
        F: FnOnce() -> T,
    {
        self.metric
            .with_label_values(&labels.label_values())
            .observe_closure_duration(f)
    }

    /// Return accumulated sum of all samples, using the provided `labels`.
    pub fn get_sample_sum(&self, labels: &L) -> f64 {
        self.metric
            .with_label_values(&labels.label_values())
            .get_sample_sum()
    }

    /// Return count of all samples, using the provided `labels`.
    pub fn get_sample_count(&self, labels: &L) -> u64 {
        self.metric
            .with_label_values(&labels.label_values())
            .get_sample_count()
    }
}