metrics_observer_yaml/
lib.rs

1//! Observes metrics in YAML format.
2//!
3//! Metric scopes are used to provide the hierarchy and indentation of metrics.  As an example, for
4//! a snapshot with two metrics — `server.msgs_received` and `server.msgs_sent` — we would
5//! expect to see this output:
6//!
7//! ```c
8//! server:
9//!   msgs_received: 42
10//!   msgs_sent: 13
11//! ```
12//!
13//! If we added another metric — `configuration_reloads` — we would expect to see:
14//!
15//! ```c
16//! configuration_reloads: 2
17//! server:
18//!   msgs_received: 42
19//!   msgs_sent: 13
20//! ```
21//!
22//! Metrics are sorted alphabetically.
23//!
24//! ## Histograms
25//!
26//! Histograms are rendered with a configurable set of quantiles that are provided when creating an
27//! instance of `YamlBuilder`.  They are formatted using human-readable labels when displayed to
28//! the user.  For example, 0.0 is rendered as "min", 1.0 as "max", and anything in between using
29//! the common "pXXX" format i.e. a quantile of 0.5 or percentile of 50 would be p50, a quantile of
30//! 0.999 or percentile of 99.9 would be p999, and so on.
31//!
32//! All histograms have the sample count of the histogram provided in the output.
33//!
34//! ```c
35//! connect_time count: 15
36//! connect_time min: 1334
37//! connect_time p50: 1934
38//! connect_time p99: 5330
39//! connect_time max: 139389
40//! ```
41//!
42#![deny(missing_docs)]
43use hdrhistogram::Histogram;
44use metrics_core::{Builder, Drain, Key, Label, Observer};
45use metrics_util::{parse_quantiles, MetricsTree, Quantile};
46use std::collections::HashMap;
47
48/// Builder for [`YamlObserver`].
49pub struct YamlBuilder {
50    quantiles: Vec<Quantile>,
51}
52
53impl YamlBuilder {
54    /// Creates a new [`YamlBuilder`] with default values.
55    pub fn new() -> Self {
56        let quantiles = parse_quantiles(&[0.0, 0.5, 0.9, 0.95, 0.99, 0.999, 1.0]);
57
58        Self { quantiles }
59    }
60
61    /// Sets the quantiles to use when rendering histograms.
62    ///
63    /// Quantiles represent a scale of 0 to 1, where percentiles represent a scale of 1 to 100, so
64    /// a quantile of 0.99 is the 99th percentile, and a quantile of 0.99 is the 99.9th percentile.
65    ///
66    /// By default, the quantiles will be set to: 0.0, 0.5, 0.9, 0.95, 0.99, 0.999, and 1.0.
67    pub fn set_quantiles(mut self, quantiles: &[f64]) -> Self {
68        self.quantiles = parse_quantiles(quantiles);
69        self
70    }
71}
72
73impl Builder for YamlBuilder {
74    type Output = YamlObserver;
75
76    fn build(&self) -> Self::Output {
77        YamlObserver {
78            quantiles: self.quantiles.clone(),
79            tree: MetricsTree::default(),
80            histos: HashMap::new(),
81        }
82    }
83}
84
85impl Default for YamlBuilder {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91/// Observess metrics in YAML format.
92pub struct YamlObserver {
93    pub(crate) quantiles: Vec<Quantile>,
94    pub(crate) tree: MetricsTree,
95    pub(crate) histos: HashMap<Key, Histogram<u64>>,
96}
97
98impl Observer for YamlObserver {
99    fn observe_counter(&mut self, key: Key, value: u64) {
100        let (levels, name) = key_to_parts(key);
101        self.tree.insert_value(levels, name, value);
102    }
103
104    fn observe_gauge(&mut self, key: Key, value: i64) {
105        let (levels, name) = key_to_parts(key);
106        self.tree.insert_value(levels, name, value);
107    }
108
109    fn observe_histogram(&mut self, key: Key, values: &[u64]) {
110        let entry = self
111            .histos
112            .entry(key)
113            .or_insert_with(|| Histogram::<u64>::new(3).expect("failed to create histogram"));
114
115        for value in values {
116            entry
117                .record(*value)
118                .expect("failed to observe histogram value");
119        }
120    }
121}
122
123impl Drain<String> for YamlObserver {
124    fn drain(&mut self) -> String {
125        for (key, h) in self.histos.drain() {
126            let (levels, name) = key_to_parts(key);
127            let values = hist_to_values(name, h.clone(), &self.quantiles);
128            self.tree.insert_values(levels, values);
129        }
130
131        let rendered = serde_yaml::to_string(&self.tree).expect("failed to render yaml output");
132        self.tree.clear();
133        rendered
134    }
135}
136
137fn key_to_parts(key: Key) -> (Vec<String>, String) {
138    let (name, labels) = key.into_parts();
139    let mut parts = name.split('.').map(ToOwned::to_owned).collect::<Vec<_>>();
140    let name = parts.pop().expect("name didn't have a single part");
141
142    let labels = labels
143        .into_iter()
144        .map(Label::into_parts)
145        .map(|(k, v)| format!("{}=\"{}\"", k, v))
146        .collect::<Vec<_>>()
147        .join(",");
148    let label = if labels.is_empty() {
149        String::new()
150    } else {
151        format!("{{{}}}", labels)
152    };
153
154    let fname = format!("{}{}", name, label);
155
156    (parts, fname)
157}
158
159fn hist_to_values(
160    name: String,
161    hist: Histogram<u64>,
162    quantiles: &[Quantile],
163) -> Vec<(String, u64)> {
164    let mut values = Vec::new();
165
166    values.push((format!("{} count", name), hist.len()));
167    for quantile in quantiles {
168        let value = hist.value_at_quantile(quantile.value());
169        values.push((format!("{} {}", name, quantile.label()), value));
170    }
171
172    values
173}