tacho/
prometheus.rs

1use super::Report;
2use hdrhistogram::Histogram;
3use std::fmt;
4use std::sync::Arc;
5
6pub fn string(report: &Report) -> Result<String, fmt::Error> {
7    let mut out = String::with_capacity(8 * 1024);
8    write(&mut out, report)?;
9    Ok(out)
10}
11
12/// Renders a `Report` for Prometheus.
13pub fn write<W>(out: &mut W, report: &Report) -> fmt::Result
14where
15    W: fmt::Write,
16{
17    for (k, v) in report.counters() {
18        let name = FmtName::new(k.prefix(), k.name());
19        write_metric(out, &name, &k.labels().into(), v)?;
20    }
21
22    for (k, v) in report.gauges() {
23        let name = FmtName::new(k.prefix(), k.name());
24        write_metric(out, &name, &k.labels().into(), v)?;
25    }
26
27    for (k, h) in report.stats() {
28        let name = FmtName::new(k.prefix(), k.name());
29        let labels = k.labels().into();
30        let count = h.count();
31        write_metric(out, &format_args!("{}_{}", name, "count"), &labels, &count)?;
32        if count > 0 {
33            write_buckets(out, &name, &labels, h.histogram())?;
34            write_metric(out, &format_args!("{}_{}", name, "min"), &labels, &h.min())?;
35            write_metric(out, &format_args!("{}_{}", name, "max"), &labels, &h.max())?;
36            write_metric(out, &format_args!("{}_{}", name, "sum"), &labels, &h.sum())?;
37        }
38    }
39
40    Ok(())
41}
42
43fn write_buckets<N, W>(out: &mut W, name: &N, labels: &FmtLabels, h: &Histogram<u64>) -> fmt::Result
44where
45    N: fmt::Display,
46    W: fmt::Write,
47{
48    // `Histogram` tracks buckets as a sequence of minimum values and incremental counts,
49    // however prometheus expects maximum values with cumulative counts.
50    //
51    // XXX Currently, we use the highest-granularity histogram available. This probably
52    // isn't practical.
53    let mut accum = 0u64;
54    let mut count = 0u64;
55    for bucket in h.iter_recorded() {
56        if count > 0 {
57            write_bucket(out, name, labels, &(bucket.value_iterated_to() - 1), accum)?;
58        }
59        count = bucket.count_at_value();
60        accum += count;
61    }
62    if count > 0 {
63        // Be explicit about the last bucket.
64        write_bucket(out, name, labels, &h.max(), accum)?;
65    }
66    if accum > 0 {
67        // Required to tell prom that the total count.
68        write_bucket(out, name, labels, &"+Inf", accum)?;
69    }
70    Ok(())
71}
72
73fn write_bucket<N, M, W>(
74    out: &mut W,
75    name: &N,
76    labels: &FmtLabels,
77    le: &M,
78    count: u64,
79) -> fmt::Result
80where
81    N: fmt::Display,
82    M: fmt::Display,
83    W: fmt::Write,
84{
85    write_metric(
86        out,
87        &format_args!("{}_bucket", name),
88        &labels.with_extra("le", format_args!("{}", le)),
89        &count,
90    )
91}
92
93fn write_metric<W, N, V>(out: &mut W, name: &N, labels: &FmtLabels, v: &V) -> fmt::Result
94where
95    W: fmt::Write,
96    N: fmt::Display,
97    V: fmt::Display,
98{
99    writeln!(out, "{}{} {}", name, labels, v)
100}
101
102fn write_prefix<W>(out: &mut W, prefix: Arc<super::Prefix>) -> fmt::Result
103where
104    W: fmt::Write,
105{
106    if let super::Prefix::Node { ref prefix, value } = *prefix {
107        write_prefix(out, prefix.clone())?;
108        write!(out, "{}:", value)?;
109    }
110    Ok(())
111}
112
113/// Formats a prefixed name.
114struct FmtName<'a> {
115    prefix: &'a Arc<super::Prefix>,
116    name: &'a str,
117}
118
119impl<'a> FmtName<'a> {
120    fn new(prefix: &'a Arc<super::Prefix>, name: &'a str) -> Self {
121        FmtName { prefix, name }
122    }
123}
124
125impl<'a> fmt::Display for FmtName<'a> {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        write_prefix(f, self.prefix.clone())?;
128        write!(f, "{}", self.name)?;
129        Ok(())
130    }
131}
132
133impl<'a> From<&'a super::Labels> for FmtLabels<'a> {
134    fn from(base: &'a super::Labels) -> Self {
135        FmtLabels { base, extra: None }
136    }
137}
138
139/// Formats labels.
140struct FmtLabels<'a> {
141    /// Labels from the original Key.
142    base: &'a super::Labels,
143    /// An export-specific label (for buckets, etc).
144    extra: Option<(&'static str, fmt::Arguments<'a>)>,
145}
146
147impl<'a> FmtLabels<'a> {
148    fn is_empty(&self) -> bool {
149        self.base.is_empty() && self.extra.is_none()
150    }
151
152    /// Creates a new FmtLabels sharing a common `base` with a new copy of `extra`.
153    fn with_extra(&'a self, k: &'static str, v: fmt::Arguments<'a>) -> FmtLabels<'a> {
154        FmtLabels {
155            base: self.base,
156            extra: Some((k, v)),
157        }
158    }
159}
160
161impl<'a> fmt::Display for FmtLabels<'a> {
162    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        if self.is_empty() {
164            return Ok(());
165        }
166
167        let mut first = true;
168        write!(f, "{{")?;
169        if let Some((k, v)) = self.extra {
170            write!(f, "{}=\"{}\"", k, v)?;
171            first = false;
172        }
173        for (k, v) in self.base.iter() {
174            if !first {
175                write!(f, ", ")?;
176            }
177            write!(f, "{}=\"{}\"", k, v)?;
178            first = false;
179        }
180        write!(f, "}}")?;
181
182        Ok(())
183    }
184}