tracing-calltree 0.1.2

Always-on hierarchical profiling for Rust tracing spans with rolling latency statistics.
Documentation
use crate::layer::CallTreeLayer;
use crate::snapshot::CallTreeSnapshot;
use crate::tree::CallTreeState;
use std::fmt;
use std::sync::Arc;
use tracing::Metadata;

pub(crate) const DEFAULT_WINDOW_SIZE: usize = 100;
pub(crate) const DEFAULT_MAX_NODES: usize = 10_000;
pub(crate) const DEFAULT_MAX_DEPTH: usize = 64;

type MetadataFilter = dyn for<'a> Fn(&Metadata<'a>) -> bool + Send + Sync + 'static;

pub(crate) struct Config {
    pub(crate) window_size: usize,
    pub(crate) max_nodes: usize,
    pub(crate) max_depth: usize,
    pub(crate) filter: Arc<MetadataFilter>,
}

pub(crate) struct CallTreeInner {
    pub(crate) config: Config,
    pub(crate) state: CallTreeState,
}

#[derive(Clone)]
pub struct CallTree {
    pub(crate) inner: Arc<CallTreeInner>,
}

impl CallTree {
    pub fn new() -> Self {
        Self::builder().build()
    }

    pub fn builder() -> CallTreeBuilder {
        CallTreeBuilder::default()
    }

    pub fn layer(&self) -> CallTreeLayer {
        CallTreeLayer::new(self.inner.clone())
    }

    pub fn snapshot(&self) -> CallTreeSnapshot {
        self.inner.state.snapshot()
    }

    pub fn internal_stats(&self) -> InternalStats {
        InternalStats {
            nodes: self.inner.state.node_count(),
            rejected_nodes: self.inner.state.rejected_nodes(),
            dropped_samples: self.inner.state.dropped_samples(),
        }
    }
}

impl Default for CallTree {
    fn default() -> Self {
        Self::new()
    }
}

pub struct CallTreeBuilder {
    window_size: usize,
    max_nodes: usize,
    max_depth: usize,
    filter: Arc<MetadataFilter>,
}

impl Default for CallTreeBuilder {
    fn default() -> Self {
        Self {
            window_size: DEFAULT_WINDOW_SIZE,
            max_nodes: DEFAULT_MAX_NODES,
            max_depth: DEFAULT_MAX_DEPTH,
            filter: Arc::new(|_| true),
        }
    }
}

impl fmt::Debug for CallTreeBuilder {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CallTreeBuilder")
            .field("window_size", &self.window_size)
            .field("max_nodes", &self.max_nodes)
            .field("max_depth", &self.max_depth)
            .finish_non_exhaustive()
    }
}

impl CallTreeBuilder {
    pub fn window_size(mut self, window_size: usize) -> Self {
        self.window_size = window_size;
        self
    }

    pub fn max_nodes(mut self, max_nodes: usize) -> Self {
        self.max_nodes = max_nodes;
        self
    }

    pub fn max_depth(mut self, max_depth: usize) -> Self {
        self.max_depth = max_depth;
        self
    }

    pub fn filter<F>(mut self, filter: F) -> Self
    where
        F: for<'a> Fn(&Metadata<'a>) -> bool + Send + Sync + 'static,
    {
        self.filter = Arc::new(filter);
        self
    }

    pub fn build(self) -> CallTree {
        CallTree {
            inner: Arc::new(CallTreeInner {
                config: Config {
                    window_size: self.window_size,
                    max_nodes: self.max_nodes,
                    max_depth: self.max_depth,
                    filter: self.filter,
                },
                state: CallTreeState::default(),
            }),
        }
    }
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct InternalStats {
    pub nodes: usize,
    pub rejected_nodes: u64,
    pub dropped_samples: u64,
}