metrix/instruments/
panel.rs

1use std::time::{Duration, Instant};
2
3use crate::snapshot::{ItemKind, Snapshot};
4use crate::util;
5use crate::{Descriptive, HandlesObservations, Observation, PutsSnapshot};
6
7use super::*;
8
9/// The panel shows recorded
10/// observations with the same label
11/// in different representations.
12///
13/// Let's say you want to monitor the successful requests
14/// of a specific endpoint of your REST API.
15/// You would then create a panel for this and might
16/// want to add a counter and a meter and a histogram
17/// to track latencies.
18///
19/// # Example
20///
21/// ```
22/// use std::time::Instant;
23/// use metrix::instruments::*;
24/// use metrix::{HandlesObservations, Observation};
25///
26/// #[derive(Clone, PartialEq, Eq)]
27/// struct SuccessfulRequests;
28///
29/// let counter = Counter::new_with_defaults("count");
30/// let gauge = Gauge::new_with_defaults("last_latency");
31/// let meter = Meter::new_with_defaults("per_second");
32/// let histogram = Histogram::new_with_defaults("latencies");
33///
34/// assert_eq!(0, counter.get());
35/// assert_eq!(None, gauge.get());
36///
37/// let mut panel = Panel::named(SuccessfulRequests, "successful_requests");
38/// panel.add_counter(counter);
39/// panel.add_gauge(gauge);
40/// panel.add_meter(meter);
41/// panel.add_histogram(histogram);
42///
43/// let observation = Observation::ObservedOneValue {
44///        label: SuccessfulRequests,
45///        value: 12.into(),
46///        timestamp: Instant::now(),
47/// };
48/// panel.handle_observation(&observation);
49/// ```
50pub struct Panel<L> {
51    label_filter: LabelFilter<L>,
52    name: Option<String>,
53    title: Option<String>,
54    description: Option<String>,
55    counter: Option<InstrumentAdapter<L, Counter>>,
56    gauge: Option<GaugeAdapter<L>>,
57    meter: Option<InstrumentAdapter<L, Meter>>,
58    histogram: Option<InstrumentAdapter<L, Histogram>>,
59    panels: Vec<Panel<L>>,
60    handlers: Vec<Box<dyn HandlesObservations<Label = L>>>,
61    snapshooters: Vec<Box<dyn PutsSnapshot>>,
62    last_update: Instant,
63    max_inactivity_duration: Option<Duration>,
64    show_activity_state: bool,
65}
66
67impl<L> Panel<L>
68where
69    L: Eq + Send + 'static,
70{
71    /// Create a new `Panel` without a name which dispatches observations
72    /// with the given label
73    pub fn new<F: Into<LabelFilter<L>>>(accept: F) -> Panel<L> {
74        Panel {
75            label_filter: accept.into(),
76            name: None,
77            title: None,
78            description: None,
79            counter: None,
80            gauge: None,
81            meter: None,
82            histogram: None,
83            panels: Vec::new(),
84            handlers: Vec::new(),
85            snapshooters: Vec::new(),
86            last_update: Instant::now(),
87            max_inactivity_duration: None,
88            show_activity_state: true,
89        }
90    }
91
92    /// Create a new `Panel` with the given name which dispatches observations
93    /// with the given label
94    pub fn named<T: Into<String>, F: Into<LabelFilter<L>>>(accept: F, name: T) -> Panel<L> {
95        let mut panel = Self::new(accept);
96        panel.name = Some(name.into());
97        panel
98    }
99
100    /// Create a new `Panel` without a name which dispatches observations
101    /// with the given labels
102    pub fn accept<F: Into<LabelFilter<L>>>(accept: F) -> Self {
103        Self::new(accept)
104    }
105
106    /// Create a new `Panel` with the given name which dispatches observations
107    /// with the given labels
108    pub fn accept_named<T: Into<String>, F: Into<LabelFilter<L>>>(accept: F, name: T) -> Self {
109        Self::named(accept, name)
110    }
111
112    /// Create a new `Panel` with the given name which dispatches all
113    /// observations
114    pub fn accept_all_named<T: Into<String>>(name: T) -> Panel<L> {
115        Self::named(AcceptAllLabels, name)
116    }
117
118    /// Create a new `Panel` without a name which dispatches all
119    /// observations
120    pub fn accept_all() -> Panel<L> {
121        Self::new(AcceptAllLabels)
122    }
123
124    pub fn add_counter<I: Into<InstrumentAdapter<L, Counter>>>(&mut self, counter: I) {
125        if self.counter.is_none() {
126            self.counter = Some(counter.into());
127        } else {
128            self.add_handler(counter.into())
129        }
130    }
131
132    pub fn counter<I: Into<InstrumentAdapter<L, Counter>>>(mut self, counter: I) -> Self {
133        self.add_counter(counter);
134        self
135    }
136
137    pub fn gauge<I: Into<GaugeAdapter<L>>>(mut self, gauge: I) -> Self {
138        self.add_gauge(gauge);
139        self
140    }
141
142    pub fn add_gauge<I: Into<GaugeAdapter<L>>>(&mut self, gauge: I) {
143        if self.gauge.is_none() {
144            self.gauge = Some(gauge.into());
145        } else {
146            self.add_handler(gauge.into())
147        }
148    }
149
150    pub fn meter<I: Into<InstrumentAdapter<L, Meter>>>(mut self, meter: I) -> Self {
151        self.add_meter(meter);
152        self
153    }
154
155    pub fn add_meter<I: Into<InstrumentAdapter<L, Meter>>>(&mut self, meter: I) {
156        if self.meter.is_none() {
157            self.meter = Some(meter.into());
158        } else {
159            self.add_handler(meter.into())
160        }
161    }
162
163    pub fn add_histogram<I: Into<InstrumentAdapter<L, Histogram>>>(&mut self, histogram: I) {
164        if self.histogram.is_none() {
165            self.histogram = Some(histogram.into());
166        } else {
167            self.add_handler(histogram.into())
168        }
169    }
170
171    pub fn histogram<I: Into<InstrumentAdapter<L, Histogram>>>(mut self, histogram: I) -> Self {
172        self.add_histogram(histogram);
173        self
174    }
175
176    pub fn add_snapshooter<T: PutsSnapshot>(&mut self, snapshooter: T) {
177        self.snapshooters.push(Box::new(snapshooter));
178    }
179
180    pub fn snapshooter<T: PutsSnapshot>(mut self, snapshooter: T) -> Self {
181        self.add_snapshooter(snapshooter);
182        self
183    }
184
185    pub fn add_instrument<I: Instrument>(&mut self, instrument: I) {
186        self.handlers
187            .push(Box::new(InstrumentAdapter::new(instrument)));
188    }
189
190    pub fn instrument<T: Instrument>(mut self, instrument: T) -> Self {
191        self.add_instrument(instrument);
192        self
193    }
194
195    pub fn add_panel(&mut self, panel: Panel<L>) {
196        self.panels.push(panel);
197    }
198
199    pub fn panel(mut self, panel: Panel<L>) -> Self {
200        self.add_panel(panel);
201        self
202    }
203
204    pub fn add_handler<H: HandlesObservations<Label = L>>(&mut self, handler: H) {
205        self.handlers.push(Box::new(handler));
206    }
207
208    pub fn handler<H: HandlesObservations<Label = L>>(mut self, handler: H) -> Self {
209        self.add_handler(handler);
210        self
211    }
212
213    /// Gets the name of this `Panel`
214    pub fn name(&self) -> Option<&str> {
215        self.name.as_deref()
216    }
217
218    /// Set the name if this `Panel`.
219    ///
220    /// The name is a path segment within a `Snapshot`
221    pub fn set_name<T: Into<String>>(&mut self, name: T) {
222        self.name = Some(name.into());
223    }
224
225    /// Sets the `title` of this `Panel`.
226    ///
227    /// A title can be part of a descriptive `Snapshot`
228    pub fn set_title<T: Into<String>>(&mut self, title: T) {
229        self.title = Some(title.into())
230    }
231
232    /// Sets the `description` of this `Panel`.
233    ///
234    /// A description can be part of a descriptive `Snapshot`
235    pub fn set_description<T: Into<String>>(&mut self, description: T) {
236        self.description = Some(description.into())
237    }
238
239    /// Sets the maximum amount of time this panel may be
240    /// inactive until no more snapshots are taken
241    ///
242    /// Default is no inactivity tracking.
243    pub fn inactivity_limit(mut self, limit: Duration) -> Self {
244        self.set_inactivity_limit(limit);
245        self
246    }
247
248    /// Set whether to show if the panel is inactive or not
249    /// if `inactivity_limit` is set.
250    ///
251    /// The default is `true`. Only has an effect if a `inactivity_limit`
252    /// is set.
253    pub fn set_inactivity_limit(&mut self, limit: Duration) {
254        self.max_inactivity_duration = Some(limit);
255    }
256
257    /// Set whether to show if the panel is inactive or not
258    /// if `inactivity_limit` is set.
259    ///
260    /// The default is `true`. Only has an effect if a `inactivity_limit`
261    /// is set.
262    pub fn set_show_activity_state(&mut self, show: bool) {
263        self.show_activity_state = show;
264    }
265
266    /// Set whether to show if the instrument is inactive are not
267    /// if `inactivity_limit` is set.
268    ///
269    /// The default is `true`. Only has an effect if a `inactivity_limit`
270    /// is set.
271    pub fn show_activity_state(mut self, show: bool) -> Self {
272        self.set_show_activity_state(show);
273        self
274    }
275
276    pub fn accepts_label(&self, label: &L) -> bool {
277        self.label_filter.accepts(label)
278    }
279
280    fn put_values_into_snapshot(&self, into: &mut Snapshot, descriptive: bool) {
281        util::put_default_descriptives(self, into, descriptive);
282        if let Some(d) = self.max_inactivity_duration {
283            if self.show_activity_state {
284                if self.last_update.elapsed() > d {
285                    into.items
286                        .push(("_inactive".to_string(), ItemKind::Boolean(true)));
287                    into.items
288                        .push(("_active".to_string(), ItemKind::Boolean(false)));
289                    return;
290                } else {
291                    into.items
292                        .push(("_inactive".to_string(), ItemKind::Boolean(false)));
293                    into.items
294                        .push(("_active".to_string(), ItemKind::Boolean(true)));
295                }
296            }
297        };
298        self.counter
299            .as_ref()
300            .iter()
301            .for_each(|x| x.put_snapshot(into, descriptive));
302        self.gauge
303            .as_ref()
304            .iter()
305            .for_each(|x| x.put_snapshot(into, descriptive));
306        self.meter
307            .as_ref()
308            .iter()
309            .for_each(|x| x.put_snapshot(into, descriptive));
310        self.histogram
311            .as_ref()
312            .iter()
313            .for_each(|x| x.put_snapshot(into, descriptive));
314        self.panels
315            .iter()
316            .for_each(|p| p.put_snapshot(into, descriptive));
317        self.snapshooters
318            .iter()
319            .for_each(|p| p.put_snapshot(into, descriptive));
320        self.handlers
321            .iter()
322            .for_each(|p| p.put_snapshot(into, descriptive));
323    }
324}
325
326impl<L> PutsSnapshot for Panel<L>
327where
328    L: Eq + Send + 'static,
329{
330    fn put_snapshot(&self, into: &mut Snapshot, descriptive: bool) {
331        if let Some(ref name) = self.name {
332            let mut new_level = Snapshot::default();
333            self.put_values_into_snapshot(&mut new_level, descriptive);
334            into.items
335                .push((name.clone(), ItemKind::Snapshot(new_level)));
336        } else {
337            self.put_values_into_snapshot(into, descriptive);
338        }
339    }
340}
341
342impl<L> HandlesObservations for Panel<L>
343where
344    L: Eq + Send + 'static,
345{
346    type Label = L;
347
348    fn handle_observation(&mut self, observation: &Observation<Self::Label>) -> usize {
349        if !self.label_filter.accepts(observation.label()) {
350            return 0;
351        }
352
353        let mut instruments_updated = 0;
354
355        self.counter
356            .iter_mut()
357            .for_each(|x| instruments_updated += x.handle_observation(&observation));
358        self.gauge
359            .iter_mut()
360            .for_each(|x| instruments_updated += x.handle_observation(&observation));
361        self.meter
362            .iter_mut()
363            .for_each(|x| instruments_updated += x.handle_observation(&observation));
364        self.histogram
365            .iter_mut()
366            .for_each(|x| instruments_updated += x.handle_observation(&observation));
367        self.panels
368            .iter_mut()
369            .for_each(|x| instruments_updated += x.handle_observation(&observation));
370        self.handlers
371            .iter_mut()
372            .for_each(|x| instruments_updated += x.handle_observation(&observation));
373
374        instruments_updated
375    }
376}
377
378impl<L> Descriptive for Panel<L> {
379    fn title(&self) -> Option<&str> {
380        self.title.as_deref()
381    }
382
383    fn description(&self) -> Option<&str> {
384        self.description.as_deref()
385    }
386}