tacho/
lib.rs

1//! A thread-safe, `Future`-aware metrics library.
2//!
3//! Many programs need to information about runtime performance: the number of requests
4//! served, a distribution of request latency, the number of failures, the number of loop
5//! iterations, etc. `tacho::new` creates a shareable, scopable metrics registry and a
6//! `Reporter`. The `Scope` supports the creation of `Counter`, `Gauge`, and `Stat`
7//! handles that may be used to report values. Each of these receivers maintains a weak
8//! reference back to the central stats registry.
9//!
10//! ## Performance
11//!
12//! Labels are stored in a `BTreeMap` because they are used as hash keys and, therefore,
13//! need to implement `Hash`.
14
15//#![cfg_attr(test, feature(test))]
16
17extern crate futures;
18extern crate hdrhistogram;
19#[macro_use]
20extern crate log;
21extern crate indexmap;
22// #[cfg(test)]
23// extern crate test;
24
25use futures::future::FutureExt;
26use futures::task::Poll;
27use hdrhistogram::Histogram;
28use indexmap::IndexMap;
29use pin_project::pin_project;
30use std::collections::BTreeMap;
31use std::fmt;
32use std::future::Future;
33use std::pin::Pin;
34use std::sync::atomic::{AtomicUsize, Ordering};
35use std::sync::{Arc, Mutex, Weak};
36use std::time::Instant;
37
38pub mod prometheus;
39mod report;
40mod timing;
41
42pub use report::{Report, Reporter};
43pub use timing::Timing;
44
45type Labels = BTreeMap<&'static str, String>;
46type CounterMap = IndexMap<Key, Arc<AtomicUsize>>;
47type GaugeMap = IndexMap<Key, Arc<AtomicUsize>>;
48type StatMap = IndexMap<Key, Arc<Mutex<HistogramWithSum>>>;
49
50#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
51pub enum Prefix {
52    Root,
53    Node {
54        prefix: Arc<Prefix>,
55        value: &'static str,
56    },
57}
58
59/// Creates a metrics registry.
60///
61/// The returned `Scope` may be you used to instantiate metrics. Labels may be attached to
62/// the scope so that all metrics created by this `Scope` are annotated.
63///
64/// The returned `Reporter` supports consumption of metrics values.
65pub fn new() -> (Scope, Reporter) {
66    let registry = Arc::new(Mutex::new(Registry::default()));
67
68    let scope = Scope {
69        labels: Labels::default(),
70        prefix: Arc::new(Prefix::Root),
71        registry: registry.clone(),
72    };
73
74    (scope, report::new(registry))
75}
76
77/// Describes a metric.
78#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
79pub struct Key {
80    name: &'static str,
81    prefix: Arc<Prefix>,
82    labels: Labels,
83}
84impl Key {
85    fn new(name: &'static str, prefix: Arc<Prefix>, labels: Labels) -> Key {
86        Key {
87            name,
88            prefix,
89            labels,
90        }
91    }
92
93    pub fn name(&self) -> &'static str {
94        self.name
95    }
96    pub fn prefix(&self) -> &Arc<Prefix> {
97        &self.prefix
98    }
99    pub fn labels(&self) -> &Labels {
100        &self.labels
101    }
102}
103
104#[derive(Default)]
105pub struct Registry {
106    counters: CounterMap,
107    gauges: GaugeMap,
108    stats: StatMap,
109}
110
111/// Supports creation of scoped metrics.
112///
113/// `Scope`s may be cloned without copying the underlying metrics registry.
114///
115/// Labels may be attached to the scope so that all metrics created by the `Scope` are
116/// labeled.
117#[derive(Clone)]
118pub struct Scope {
119    labels: Labels,
120    prefix: Arc<Prefix>,
121    registry: Arc<Mutex<Registry>>,
122}
123
124impl Scope {
125    /// Accesses scoping labels.
126    pub fn labels(&self) -> &Labels {
127        &self.labels
128    }
129
130    /// Adds a label into scope (potentially overwriting).
131    pub fn labeled<D: fmt::Display>(mut self, k: &'static str, v: D) -> Self {
132        self.labels.insert(k, format!("{}", v));
133        self
134    }
135
136    /// Appends a prefix to the current scope.
137    pub fn prefixed(mut self, value: &'static str) -> Self {
138        let p = Prefix::Node {
139            prefix: self.prefix,
140            value,
141        };
142        self.prefix = Arc::new(p);
143        self
144    }
145
146    /// Creates a Counter with the given name.
147    pub fn counter(&self, name: &'static str) -> Counter {
148        let key = Key::new(name, self.prefix.clone(), self.labels.clone());
149        let mut reg = self
150            .registry
151            .lock()
152            .expect("failed to obtain lock on registry");
153
154        if let Some(c) = reg.counters.get(&key) {
155            return Counter(Arc::downgrade(c));
156        }
157
158        let c = Arc::new(AtomicUsize::new(0));
159        let counter = Counter(Arc::downgrade(&c));
160        reg.counters.insert(key, c);
161        counter
162    }
163
164    /// Creates a Gauge with the given name.
165    pub fn gauge(&self, name: &'static str) -> Gauge {
166        let key = Key::new(name, self.prefix.clone(), self.labels.clone());
167        let mut reg = self
168            .registry
169            .lock()
170            .expect("failed to obtain lock on registry");
171
172        if let Some(g) = reg.gauges.get(&key) {
173            return Gauge(Arc::downgrade(g));
174        }
175
176        let g = Arc::new(AtomicUsize::new(0));
177        let gauge = Gauge(Arc::downgrade(&g));
178        reg.gauges.insert(key, g);
179        gauge
180    }
181
182    /// Creates a Stat with the given name.
183    ///
184    /// The underlying histogram is automatically resized as values are added.
185    pub fn stat(&self, name: &'static str) -> Stat {
186        let key = Key::new(name, self.prefix.clone(), self.labels.clone());
187        self.mk_stat(key, None)
188    }
189
190    pub fn timer_us(&self, name: &'static str) -> Timer {
191        Timer {
192            stat: self.stat(name),
193            unit: TimeUnit::Micros,
194        }
195    }
196
197    pub fn timer_ms(&self, name: &'static str) -> Timer {
198        Timer {
199            stat: self.stat(name),
200            unit: TimeUnit::Millis,
201        }
202    }
203
204    /// Creates a Stat with the given name and histogram paramters.
205    pub fn stat_with_bounds(&self, name: &'static str, low: u64, high: u64) -> Stat {
206        let key = Key::new(name, self.prefix.clone(), self.labels.clone());
207        self.mk_stat(key, Some((low, high)))
208    }
209
210    fn mk_stat(&self, key: Key, bounds: Option<(u64, u64)>) -> Stat {
211        let mut reg = self
212            .registry
213            .lock()
214            .expect("failed to obtain lock on registry");
215
216        if let Some(h) = reg.stats.get(&key) {
217            let histo = Arc::downgrade(h);
218            return Stat { histo, bounds };
219        }
220
221        let h = Arc::new(Mutex::new(HistogramWithSum::new(bounds)));
222        let histo = Arc::downgrade(&h);
223        reg.stats.insert(key, h);
224        Stat { histo, bounds }
225    }
226}
227
228/// Counts monotically.
229#[derive(Clone)]
230pub struct Counter(Weak<AtomicUsize>);
231impl Counter {
232    pub fn incr(&self, v: usize) {
233        if let Some(c) = self.0.upgrade() {
234            c.fetch_add(v, Ordering::AcqRel);
235        }
236    }
237}
238
239/// Captures an instantaneous value.
240#[derive(Clone)]
241pub struct Gauge(Weak<AtomicUsize>);
242impl Gauge {
243    pub fn incr(&self, v: usize) {
244        if let Some(g) = self.0.upgrade() {
245            g.fetch_add(v, Ordering::AcqRel);
246        } else {
247            debug!("gauge dropped");
248        }
249    }
250    pub fn decr(&self, v: usize) {
251        if let Some(g) = self.0.upgrade() {
252            g.fetch_sub(v, Ordering::AcqRel);
253        } else {
254            debug!("gauge dropped");
255        }
256    }
257    pub fn set(&self, v: usize) {
258        if let Some(g) = self.0.upgrade() {
259            g.store(v, Ordering::Release);
260        } else {
261            debug!("gauge dropped");
262        }
263    }
264}
265
266/// Histograms hold up to 4 significant figures.
267const HISTOGRAM_PRECISION: u8 = 4;
268
269/// Tracks a distribution of values with their sum.
270///
271/// `hdrhistogram::Histogram` does not track a sum by default; but prometheus expects a `sum`
272/// for histograms.
273#[derive(Clone)]
274pub struct HistogramWithSum {
275    histogram: Histogram<u64>,
276    sum: u64,
277}
278
279impl HistogramWithSum {
280    /// Constructs a new `HistogramWithSum`, possibly with bounds.
281    fn new(bounds: Option<(u64, u64)>) -> Self {
282        let h = match bounds {
283            None => Histogram::<u64>::new(HISTOGRAM_PRECISION),
284            Some((l, h)) => Histogram::<u64>::new_with_bounds(l, h, HISTOGRAM_PRECISION),
285        };
286        let histogram = h.expect("failed to create histogram");
287        HistogramWithSum { histogram, sum: 0 }
288    }
289
290    /// Record a value to
291    fn record(&mut self, v: u64) {
292        if let Err(e) = self.histogram.record(v) {
293            error!("failed to add value to histogram: {:?}", e);
294        }
295        if v >= ::std::u64::MAX - self.sum {
296            self.sum = ::std::u64::MAX
297        } else {
298            self.sum += v;
299        }
300    }
301
302    pub fn histogram(&self) -> &Histogram<u64> {
303        &self.histogram
304    }
305    pub fn count(&self) -> u64 {
306        self.histogram.len()
307    }
308    pub fn max(&self) -> u64 {
309        self.histogram.max()
310    }
311    pub fn min(&self) -> u64 {
312        self.histogram.min()
313    }
314    pub fn sum(&self) -> u64 {
315        self.sum
316    }
317
318    pub fn clear(&mut self) {
319        self.histogram.reset();
320        self.sum = 0;
321    }
322}
323
324/// Caputres a distribution of values.
325#[derive(Clone)]
326pub struct Stat {
327    histo: Weak<Mutex<HistogramWithSum>>,
328    bounds: Option<(u64, u64)>,
329}
330
331impl Stat {
332    pub fn add(&self, v: u64) {
333        if let Some(h) = self.histo.upgrade() {
334            let mut histo = h.lock().expect("failed to obtain lock for stat");
335            histo.record(v);
336        }
337    }
338
339    pub fn add_values(&mut self, vs: &[u64]) {
340        if let Some(h) = self.histo.upgrade() {
341            let mut histo = h.lock().expect("failed to obtain lock for stat");
342            for v in vs {
343                histo.record(*v)
344            }
345        }
346    }
347}
348
349#[derive(Clone)]
350pub struct Timer {
351    stat: Stat,
352    unit: TimeUnit,
353}
354#[derive(Copy, Clone)]
355pub enum TimeUnit {
356    Millis,
357    Micros,
358}
359impl Timer {
360    pub fn record_since(&self, t0: Instant) {
361        self.stat.add(to_u64(t0, self.unit));
362    }
363
364    pub fn time<F: Future>(&self, fut: F) -> Timed<impl Future<Output = F::Output>> {
365        let stat = self.stat.clone();
366        let unit = self.unit;
367        let f = futures::future::lazy(|_| {
368            // Start timing once the future is actually being invoked (and not
369            // when the object is created).
370            Timing::start()
371        })
372        .then(move |t0| {
373            fut.map(move |v| {
374                stat.add(to_u64(t0, unit));
375                v
376            })
377        });
378        Timed(f)
379    }
380}
381
382fn to_u64(t0: Instant, unit: TimeUnit) -> u64 {
383    match unit {
384        TimeUnit::Millis => t0.elapsed_ms(),
385        TimeUnit::Micros => t0.elapsed_us(),
386    }
387}
388
389#[pin_project]
390pub struct Timed<F>(#[pin] F);
391impl<F: Future> Future for Timed<F> {
392    type Output = F::Output;
393    fn poll(self: Pin<&mut Self>, cx: &mut futures::task::Context) -> Poll<Self::Output> {
394        self.project().0.poll(cx)
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    #[test]
401    fn test_report_peek() {
402        let (metrics, reporter) = super::new();
403        let metrics = metrics.labeled("joy", "painting");
404
405        let happy_accidents = metrics.counter("happy_accidents");
406        let paint_level = metrics.gauge("paint_level");
407        let mut stroke_len = metrics.stat("stroke_len");
408
409        happy_accidents.incr(1);
410        paint_level.set(2);
411        stroke_len.add_values(&[1, 2, 3]);
412
413        {
414            let report = reporter.peek();
415            {
416                let k = report
417                    .counters()
418                    .keys()
419                    .find(|k| k.name() == "happy_accidents")
420                    .expect("expected counter: happy_accidents");
421                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
422                assert_eq!(report.counters().get(k), Some(&1));
423            }
424            {
425                let k = report
426                    .gauges()
427                    .keys()
428                    .find(|k| k.name() == "paint_level")
429                    .expect("expected gauge: paint_level");
430                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
431                assert_eq!(report.gauges().get(k), Some(&2));
432            }
433            assert_eq!(
434                report.gauges().keys().find(|k| k.name() == "brush_width"),
435                None
436            );
437            {
438                let k = report
439                    .stats()
440                    .keys()
441                    .find(|k| k.name() == "stroke_len")
442                    .expect("expected stat: stroke_len");
443                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
444                assert!(report.stats().contains_key(k));
445            }
446            assert_eq!(report.stats().keys().find(|k| k.name() == "tree_len"), None);
447        }
448
449        drop(paint_level);
450        let brush_width = metrics.gauge("brush_width");
451        let mut tree_len = metrics.stat("tree_len");
452
453        happy_accidents.incr(2);
454        brush_width.set(5);
455        stroke_len.add_values(&[1, 2, 3]);
456        tree_len.add_values(&[3, 4, 5]);
457
458        {
459            let report = reporter.peek();
460            {
461                let k = report
462                    .counters()
463                    .keys()
464                    .find(|k| k.name() == "happy_accidents")
465                    .expect("expected counter: happy_accidents");
466                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
467                assert_eq!(report.counters().get(k), Some(&3));
468            }
469            {
470                let k = report
471                    .gauges()
472                    .keys()
473                    .find(|k| k.name() == "paint_level")
474                    .expect("expected gauge: paint_level");
475                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
476                assert_eq!(report.gauges().get(k), Some(&2));
477            }
478            {
479                let k = report
480                    .gauges()
481                    .keys()
482                    .find(|k| k.name() == "brush_width")
483                    .expect("expected gauge: brush_width");
484                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
485                assert_eq!(report.gauges().get(k), Some(&5));
486            }
487            {
488                let k = report
489                    .stats()
490                    .keys()
491                    .find(|k| k.name() == "stroke_len")
492                    .expect("expected stat: stroke_len");
493                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
494                assert!(report.stats().contains_key(k));
495            }
496            {
497                let k = report
498                    .stats()
499                    .keys()
500                    .find(|k| k.name() == "tree_len")
501                    .expect("expected stat: tree_len");
502                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
503                assert!(report.stats().contains_key(k));
504            }
505        }
506    }
507
508    #[test]
509    fn test_report_take() {
510        let (metrics, mut reporter) = super::new();
511        let metrics = metrics.labeled("joy", "painting");
512
513        let happy_accidents = metrics.counter("happy_accidents");
514        let paint_level = metrics.gauge("paint_level");
515        let mut stroke_len = metrics.stat("stroke_len");
516        happy_accidents.incr(1);
517        paint_level.set(2);
518        stroke_len.add_values(&[1, 2, 3]);
519        {
520            let report = reporter.take();
521            {
522                let k = report
523                    .counters()
524                    .keys()
525                    .find(|k| k.name() == "happy_accidents")
526                    .expect("expected counter: happy_accidents");
527                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
528                assert_eq!(report.counters().get(k), Some(&1));
529            }
530            {
531                let k = report
532                    .gauges()
533                    .keys()
534                    .find(|k| k.name() == "paint_level")
535                    .expect("expected gauge");
536                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
537                assert_eq!(report.gauges().get(k), Some(&2));
538            }
539            assert_eq!(
540                report.gauges().keys().find(|k| k.name() == "brush_width"),
541                None
542            );
543            {
544                let k = report
545                    .stats()
546                    .keys()
547                    .find(|k| k.name() == "stroke_len")
548                    .expect("expected stat");
549                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
550                assert!(report.stats().contains_key(k));
551            }
552            assert_eq!(report.stats().keys().find(|k| k.name() == "tree_len"), None);
553            {
554                let k = report
555                    .stats()
556                    .keys()
557                    .find(|k| k.name() == "stroke_len")
558                    .expect("expected stat");
559                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
560                assert!(report.stats().contains_key(k));
561            }
562        }
563
564        drop(paint_level);
565        drop(stroke_len);
566        {
567            let report = reporter.take();
568            {
569                let counters = report.counters();
570                let k = counters
571                    .keys()
572                    .find(|k| k.name() == "happy_accidents")
573                    .expect("expected counter: happy_accidents");
574                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
575                assert_eq!(counters.get(k), Some(&1));
576            }
577            {
578                let k = report
579                    .gauges()
580                    .keys()
581                    .find(|k| k.name() == "paint_level")
582                    .expect("expected gauge");
583                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
584                assert_eq!(report.gauges().get(k), Some(&2));
585            }
586            {
587                let k = report
588                    .stats()
589                    .keys()
590                    .find(|k| k.name() == "stroke_len")
591                    .expect("expected stat");
592                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
593                assert!(report.stats().contains_key(k));
594            }
595        }
596
597        let brush_width = metrics.gauge("brush_width");
598        let mut tree_len = metrics.stat("tree_len");
599        happy_accidents.incr(2);
600        brush_width.set(5);
601        tree_len.add_values(&[3, 4, 5]);
602        {
603            let report = reporter.take();
604            {
605                let k = report
606                    .counters()
607                    .keys()
608                    .find(|k| k.name() == "happy_accidents")
609                    .expect("expected counter: happy_accidents");
610                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
611                assert_eq!(report.counters().get(k), Some(&3));
612            }
613            assert_eq!(
614                report.gauges().keys().find(|k| k.name() == "paint_level"),
615                None
616            );
617            {
618                let k = report
619                    .gauges()
620                    .keys()
621                    .find(|k| k.name() == "brush_width")
622                    .expect("expected gauge");
623                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
624                assert_eq!(report.gauges().get(k), Some(&5));
625            }
626            assert_eq!(
627                report.stats().keys().find(|k| k.name() == "stroke_len"),
628                None
629            );
630            {
631                let k = report
632                    .stats()
633                    .keys()
634                    .find(|k| k.name() == "tree_len")
635                    .expect("expeced stat");
636                assert_eq!(k.labels.get("joy"), Some(&"painting".to_string()));
637                assert!(report.stats().contains_key(k));
638            }
639        }
640    }
641}