metrics_util/layers/
fanout.rs

1use std::sync::Arc;
2
3use metrics::{
4    Counter, CounterFn, Gauge, GaugeFn, Histogram, HistogramFn, Key, KeyName, Metadata, Recorder,
5    SharedString, Unit,
6};
7
8struct FanoutCounter {
9    counters: Vec<Counter>,
10}
11
12impl FanoutCounter {
13    pub fn from_counters(counters: Vec<Counter>) -> Self {
14        Self { counters }
15    }
16}
17
18impl CounterFn for FanoutCounter {
19    fn increment(&self, value: u64) {
20        for counter in &self.counters {
21            counter.increment(value);
22        }
23    }
24
25    fn absolute(&self, value: u64) {
26        for counter in &self.counters {
27            counter.absolute(value);
28        }
29    }
30}
31
32impl From<FanoutCounter> for Counter {
33    fn from(counter: FanoutCounter) -> Counter {
34        Counter::from_arc(Arc::new(counter))
35    }
36}
37
38struct FanoutGauge {
39    gauges: Vec<Gauge>,
40}
41
42impl FanoutGauge {
43    pub fn from_gauges(gauges: Vec<Gauge>) -> Self {
44        Self { gauges }
45    }
46}
47
48impl GaugeFn for FanoutGauge {
49    fn increment(&self, value: f64) {
50        for gauge in &self.gauges {
51            gauge.increment(value);
52        }
53    }
54
55    fn decrement(&self, value: f64) {
56        for gauge in &self.gauges {
57            gauge.decrement(value);
58        }
59    }
60
61    fn set(&self, value: f64) {
62        for gauge in &self.gauges {
63            gauge.set(value);
64        }
65    }
66}
67
68impl From<FanoutGauge> for Gauge {
69    fn from(gauge: FanoutGauge) -> Gauge {
70        Gauge::from_arc(Arc::new(gauge))
71    }
72}
73
74struct FanoutHistogram {
75    histograms: Vec<Histogram>,
76}
77
78impl FanoutHistogram {
79    pub fn from_histograms(histograms: Vec<Histogram>) -> Self {
80        Self { histograms }
81    }
82}
83
84impl HistogramFn for FanoutHistogram {
85    fn record(&self, value: f64) {
86        for histogram in &self.histograms {
87            histogram.record(value);
88        }
89    }
90}
91
92impl From<FanoutHistogram> for Histogram {
93    fn from(histogram: FanoutHistogram) -> Histogram {
94        Histogram::from_arc(Arc::new(histogram))
95    }
96}
97
98/// Fans out metrics to multiple recorders.
99pub struct Fanout {
100    recorders: Vec<Box<dyn Recorder>>,
101}
102
103impl Recorder for Fanout {
104    fn describe_counter(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
105        for recorder in &self.recorders {
106            recorder.describe_counter(key_name.clone(), unit, description.clone());
107        }
108    }
109
110    fn describe_gauge(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
111        for recorder in &self.recorders {
112            recorder.describe_gauge(key_name.clone(), unit, description.clone());
113        }
114    }
115
116    fn describe_histogram(&self, key_name: KeyName, unit: Option<Unit>, description: SharedString) {
117        for recorder in &self.recorders {
118            recorder.describe_histogram(key_name.clone(), unit, description.clone());
119        }
120    }
121
122    fn register_counter(&self, key: &Key, metadata: &Metadata<'_>) -> Counter {
123        let counters = self
124            .recorders
125            .iter()
126            .map(|recorder| recorder.register_counter(key, metadata))
127            .collect();
128
129        FanoutCounter::from_counters(counters).into()
130    }
131
132    fn register_gauge(&self, key: &Key, metadata: &Metadata<'_>) -> Gauge {
133        let gauges =
134            self.recorders.iter().map(|recorder| recorder.register_gauge(key, metadata)).collect();
135
136        FanoutGauge::from_gauges(gauges).into()
137    }
138
139    fn register_histogram(&self, key: &Key, metadata: &Metadata<'_>) -> Histogram {
140        let histograms = self
141            .recorders
142            .iter()
143            .map(|recorder| recorder.register_histogram(key, metadata))
144            .collect();
145
146        FanoutHistogram::from_histograms(histograms).into()
147    }
148}
149
150/// A layer for fanning out metrics to multiple recorders.
151///
152/// More information on the behavior of the layer can be found in [`Fanout`].
153#[derive(Default)]
154pub struct FanoutBuilder {
155    recorders: Vec<Box<dyn Recorder>>,
156}
157
158impl FanoutBuilder {
159    /// Adds a recorder to the fanout list.
160    pub fn add_recorder<R>(mut self, recorder: R) -> FanoutBuilder
161    where
162        R: Recorder + 'static,
163    {
164        self.recorders.push(Box::new(recorder));
165        self
166    }
167
168    /// Builds the `Fanout` layer.
169    pub fn build(self) -> Fanout {
170        Fanout { recorders: self.recorders }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::FanoutBuilder;
177    use crate::test_util::*;
178    use metrics::{Counter, Gauge, Histogram, Unit};
179
180    static METADATA: metrics::Metadata =
181        metrics::Metadata::new(module_path!(), metrics::Level::INFO, Some(module_path!()));
182
183    #[test]
184    fn test_basic_functionality() {
185        let operations = vec![
186            RecorderOperation::DescribeCounter(
187                "counter_key".into(),
188                Some(Unit::Count),
189                "counter desc".into(),
190            ),
191            RecorderOperation::DescribeGauge(
192                "gauge_key".into(),
193                Some(Unit::Bytes),
194                "gauge desc".into(),
195            ),
196            RecorderOperation::DescribeHistogram(
197                "histogram_key".into(),
198                Some(Unit::Nanoseconds),
199                "histogram desc".into(),
200            ),
201            RecorderOperation::RegisterCounter("counter_key".into(), Counter::noop(), &METADATA),
202            RecorderOperation::RegisterGauge("gauge_key".into(), Gauge::noop(), &METADATA),
203            RecorderOperation::RegisterHistogram(
204                "histogram_key".into(),
205                Histogram::noop(),
206                &METADATA,
207            ),
208        ];
209
210        let recorder1 = MockBasicRecorder::from_operations(operations.clone());
211        let recorder2 = MockBasicRecorder::from_operations(operations.clone());
212        let fanout =
213            FanoutBuilder::default().add_recorder(recorder1).add_recorder(recorder2).build();
214
215        for operation in operations {
216            operation.apply_to_recorder(&fanout);
217        }
218    }
219}