metrics_observer_json/
lib.rs

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