latency_trace/
lt_report_g.rs

1//! [`LatencyTraceG`] activation and measurment methods, other supporting types and/or impls.
2
3use hdrhistogram::CreationError;
4use std::{
5    error::Error,
6    fmt::{Debug, Display},
7    sync::Arc,
8};
9use tracing::Dispatch;
10use tracing_subscriber::{
11    layer::{Layered, SubscriberExt},
12    util::{SubscriberInitExt, TryInitError},
13    Registry,
14};
15
16use crate::{
17    default_span_grouper,
18    lt_collect_g::{LatencyTraceCfg, LatencyTraceG, Timing},
19    lt_refine_g::Timings,
20    tlc_param::{TlcBase, TlcDirect, TlcParam},
21};
22
23//==============
24// Errors
25
26/// Error returned by [`LatencyTrace`](crate::LatencyTrace) activation methods.
27#[derive(Debug)]
28pub enum ActivationError {
29    HistogramConfigError,
30    TracingSubscriberInitError,
31}
32
33impl Display for ActivationError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        Debug::fmt(self, f)
36    }
37}
38
39impl Error for ActivationError {}
40
41impl From<CreationError> for ActivationError {
42    fn from(_: CreationError) -> Self {
43        Self::HistogramConfigError
44    }
45}
46
47impl From<TryInitError> for ActivationError {
48    fn from(_: TryInitError) -> Self {
49        Self::TracingSubscriberInitError
50    }
51}
52
53//==============
54// impl for LatencyTraceCfg
55
56impl LatencyTraceCfg {
57    /// Validates that the configuration settings yield histograms that avoid all potential [hdrhistogram::Histogram] errors
58    /// as our histograms are `u64`, have a `hist_low` of `1`, and are auto-resizable.
59    fn validate_hist_high_sigfig(&self) -> Result<(), CreationError> {
60        let _ = Timing::new_with_bounds(1, self.hist_high, self.hist_sigfig)?;
61        Ok(())
62    }
63}
64
65impl Default for LatencyTraceCfg {
66    /// Instantiates a default [LatencyTraceCfg]. The defaults are:
67    /// - Grouping of spans using the [`default_span_grouper`], which simply groups by the span's
68    ///   callsite information, which distills the *tracing* framework's Callsite concept
69    ///   (see [Metadata and Callsite](https://docs.rs/tracing-core/0.1.31/tracing_core/)). This default can be
70    ///   modified by using the [`Self::with_span_grouper`] method.
71    /// - `hist_high` of `20,000,000` (20 seconds). This default can be modified by using the [`Self::with_hist_high`] method.
72    /// - `hist_sigfig` of 2. This default can be modified by using the [`Self::with_hist_sigfig`] method.
73    ///
74    /// See [hdrhistogram::Histogram::high] and [hdrhistogram::Histogram::sigfig] for an explanation of these histogram configuration parameters.
75    ///
76    /// Note that the histograms used here are auto-resizable, which means [`hdrhistogram::Histogram::high`] is
77    /// automatically adjusted as needed (although resizing requires memory reallocation at runtime).
78    fn default() -> Self {
79        LatencyTraceCfg {
80            span_grouper: Arc::new(default_span_grouper),
81            hist_high: 20 * 1000 * 1000,
82            hist_sigfig: 2,
83        }
84    }
85}
86
87//==============
88// impl for LatencyTrace
89
90impl<P> LatencyTraceG<P>
91where
92    P: TlcParam + Clone + 'static,
93    P::Control: TlcBase + Clone,
94    Layered<LatencyTraceG<P>, Registry>: Into<Dispatch>,
95{
96    /// Returns the active instance of `Self` if it exists.
97    pub fn active() -> Option<Self> {
98        tracing::dispatcher::get_default(|disp| {
99            let lt: &Self = disp.downcast_ref()?;
100            Some(lt.clone())
101        })
102    }
103
104    /// Returns the active instance of `Self` if it exists or activates a new instance with the given `config` otherwise.
105    /// Activation entails setting the global default [`tracing::Subscriber`], of which there can be only one and it can't
106    /// be changed once it is set.
107    ///
108    /// If a [`LatencyTrace`] has been previously activated in the same process, the `config` passed to this
109    /// function will be ignored and the current active [`LatencyTrace`] will be returned.
110    ///
111    /// # Errors
112    /// - [`ActivationError::HistogramConfigError`] if the `config`'s `hist_high` and `hist_sigfig` would cause
113    ///   [`hdrhistogram::Histogram::new_with_bounds`]`(1, hist_high, hist_sigfig)` to fail.
114    /// - [`ActivationError::TracingSubscriberInitError`] if a global [`tracing::Subscriber`] is already set and its
115    ///   type is not the same as `Self`.
116    pub fn activated(config: LatencyTraceCfg) -> Result<Self, ActivationError> {
117        config.validate_hist_high_sigfig()?;
118        let default_dispatch_exists =
119            tracing::dispatcher::get_default(|disp| disp.is::<Layered<Self, Registry>>());
120        let lt = if !default_dispatch_exists {
121            let lt_new = LatencyTraceG::new(config);
122            let reg: Layered<Self, Registry> = Registry::default().with(lt_new.clone());
123            reg.try_init()?;
124            lt_new
125        } else {
126            Self::active().expect("`if` condition should ensure `else` Ok")
127        };
128        Ok(lt)
129    }
130
131    /// Returns the active instance of `Self` if it exists or activates a new instance with the default configuration otherwise.
132    /// Activation entails setting the global default [`tracing::Subscriber`], of which there can be only one and it can't
133    /// be changed once it is set.
134    ///
135    /// If a [`LatencyTrace`] has been previously activated in the same process, the default configuration
136    /// will be ignored and the current active [`LatencyTrace`] will be returned.
137    ///
138    /// # Errors
139    /// - [`ActivationError::TracingSubscriberInitError`] if a global [`tracing::Subscriber`] is already set and its
140    ///   type is not the same as `Self`.
141    pub fn activated_default() -> Result<Self, ActivationError> {
142        Self::activated(LatencyTraceCfg::default())
143    }
144}
145
146impl<P> LatencyTraceG<P>
147where
148    P: TlcParam,
149    P::Control: TlcDirect,
150{
151    /// Executes the instrumented function `f` and, after `f` completes, returns the observed latencies.
152    pub fn measure_latencies(&self, f: impl FnOnce()) -> Timings {
153        f();
154        let acc = self.take_acc_timings();
155        self.report_timings(acc)
156    }
157}