telemetry_subscribers/
span_latency_prom.rs1use std::time::Instant;
21
22use prometheus::{Registry, exponential_buckets, register_histogram_vec_with_registry};
23use tracing::{Subscriber, span};
24
25pub struct PrometheusSpanLatencyLayer {
27 span_latencies: prometheus::HistogramVec,
28}
29
30#[derive(Debug)]
31pub enum PrometheusSpanError {
32 ZeroOrNegativeNumBuckets,
34 PromError(prometheus::Error),
35}
36
37impl From<prometheus::Error> for PrometheusSpanError {
38 fn from(err: prometheus::Error) -> Self {
39 Self::PromError(err)
40 }
41}
42
43const TOP_LATENCY_IN_NS: f64 = 300.0 * 1.0e9;
44const LOWEST_LATENCY_IN_NS: f64 = 500.0;
45
46impl PrometheusSpanLatencyLayer {
47 pub fn try_new(registry: &Registry, num_buckets: usize) -> Result<Self, PrometheusSpanError> {
51 if num_buckets < 1 {
52 return Err(PrometheusSpanError::ZeroOrNegativeNumBuckets);
53 }
54
55 let factor = (TOP_LATENCY_IN_NS / LOWEST_LATENCY_IN_NS).powf(1.0 / (num_buckets as f64));
59 let buckets = exponential_buckets(LOWEST_LATENCY_IN_NS, factor, num_buckets)?;
60 let span_latencies = register_histogram_vec_with_registry!(
61 "tracing_span_latencies",
62 "Latencies from tokio-tracing spans",
63 &["span_name"],
64 buckets,
65 registry
66 )?;
67 Ok(Self { span_latencies })
68 }
69}
70
71struct PromSpanTimestamp(Instant);
72
73impl<S> tracing_subscriber::Layer<S> for PrometheusSpanLatencyLayer
74where
75 S: Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
76{
77 fn on_new_span(
78 &self,
79 _attrs: &span::Attributes,
80 id: &span::Id,
81 ctx: tracing_subscriber::layer::Context<S>,
82 ) {
83 let span = ctx.span(id).unwrap();
84 span.extensions_mut()
88 .insert(PromSpanTimestamp(Instant::now()));
89 }
90
91 fn on_close(&self, id: span::Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
92 let span = ctx.span(&id).unwrap();
93 let start_time = span
94 .extensions()
95 .get::<PromSpanTimestamp>()
96 .expect("Could not find saved timestamp on span")
97 .0;
98 let elapsed_ns = start_time.elapsed().as_nanos() as u64;
99 self.span_latencies
100 .with_label_values(&[span.name()])
101 .observe(elapsed_ns as f64);
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_prom_span_latency_init() {
111 let registry = prometheus::Registry::new();
112
113 let res = PrometheusSpanLatencyLayer::try_new(®istry, 0);
114 assert!(matches!(
115 res,
116 Err(PrometheusSpanError::ZeroOrNegativeNumBuckets)
117 ));
118 }
119}