metrics_util/layers/
fanout.rs

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