use std::{collections::BTreeMap, sync::Arc, thread};
use hdrhistogram::Histogram;
use tracing::{
span::{Attributes, Id},
Subscriber,
};
use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};
use crate::{
lt_collect_g::LatencyTraceG,
summary_stats,
tlc_param::{Either, Joined, Probed},
SummaryStats, Wrapper,
};
pub use crate::{
lt_collect_g::{LatencyTraceCfg, Timing},
lt_refine_g::{SpanGroup, Timings, TimingsView},
lt_report_g::ActivationError,
probed_trace::ProbedTrace,
};
#[doc(hidden)]
pub type LatencyTraceJ = LatencyTraceG<Joined>;
#[doc(hidden)]
pub type LatencyTraceE = LatencyTraceG<Either>;
impl LatencyTraceE {
pub fn select_probed() {
Either::select_probed();
}
pub fn select_joined() {
Either::select_joined()
}
}
impl LatencyTraceCfg {
pub fn with_hist_high(&self, hist_high: u64) -> Self {
LatencyTraceCfg {
span_grouper: self.span_grouper.clone(),
hist_high,
hist_sigfig: self.hist_sigfig,
}
}
pub fn with_hist_sigfig(&self, hist_sigfig: u8) -> Self {
LatencyTraceCfg {
span_grouper: self.span_grouper.clone(),
hist_high: self.hist_high,
hist_sigfig,
}
}
pub fn with_span_grouper(
&self,
span_grouper: impl Fn(&Attributes) -> Vec<(String, String)> + Send + Sync + 'static,
) -> Self {
LatencyTraceCfg {
span_grouper: Arc::new(span_grouper),
hist_high: self.hist_high,
hist_sigfig: self.hist_sigfig,
}
}
}
#[derive(Clone)]
pub struct LatencyTrace(pub(crate) LatencyTraceG<Probed>);
impl LatencyTrace {
pub fn new(config: LatencyTraceCfg) -> Self {
Self(LatencyTraceG::new(config))
}
pub fn active() -> Option<Self> {
Some(Self(LatencyTraceG::active()?))
}
pub fn activated(config: LatencyTraceCfg) -> Result<Self, ActivationError> {
Ok(Self(LatencyTraceG::activated(config)?))
}
pub fn activated_default() -> Result<Self, ActivationError> {
Ok(Self(LatencyTraceG::activated_default()?))
}
pub fn measure_latencies(&self, f: impl FnOnce()) -> Timings {
self.0.measure_latencies(f)
}
pub fn measure_latencies_probed(
self,
f: impl FnOnce() + Send + 'static,
) -> Result<ProbedTrace, ActivationError> {
let pt = ProbedTrace::new(self);
let jh = thread::spawn(f);
pt.set_join_handle(jh);
Ok(pt)
}
}
impl Default for LatencyTrace {
fn default() -> Self {
Self::new(LatencyTraceCfg::default())
}
}
impl<S> Layer<S> for LatencyTrace
where
S: Subscriber,
S: for<'lookup> LookupSpan<'lookup>,
{
fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
self.0.on_new_span(attrs, id, ctx);
}
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
self.0.on_close(id, ctx);
}
}
impl SpanGroup {
pub fn name(&self) -> &'static str {
self.name
}
pub fn id(&self) -> &str {
&self.id
}
pub fn code_line(&self) -> &str {
&self.code_line
}
pub fn props(&self) -> &[(String, String)] {
&self.props
}
pub fn parent_id(&self) -> Option<&str> {
self.parent_id.iter().map(|x| x.as_ref()).next()
}
pub fn depth(&self) -> usize {
self.depth
}
}
impl<K> TimingsView<K> {
pub fn aggregate<G>(&self, f: impl Fn(&K) -> G) -> TimingsView<G>
where
G: Ord,
{
let mut res: BTreeMap<G, Histogram<u64>> = BTreeMap::new();
for (k, v) in self.iter() {
let g = f(k);
let hist = match res.get_mut(&g) {
Some(hist) => hist,
None => {
res.insert(g, Histogram::new_from(v));
res.get_mut(&f(k))
.expect("key `g == f(k)` was just inserted in `res`")
}
};
hist.add(v)
.expect("should not happen given histogram construction");
}
res.into()
}
pub fn add(&mut self, mut other: TimingsView<K>)
where
K: Ord,
{
for (k, h) in self.iter_mut() {
let other_h = other.remove(k);
if let Some(other_h) = other_h {
h.add(other_h)
.expect("should not happen given histogram construction");
}
}
for (k, h) in other.0.into_iter() {
self.insert(k, h);
}
}
pub fn summary_stats(&self) -> Wrapper<BTreeMap<K, SummaryStats>>
where
K: Ord + Clone,
{
self.map_values(summary_stats)
}
}
impl Timings {
pub fn aggregator_is_consistent<G>(&self, f: impl Fn(&SpanGroup) -> G) -> bool
where
G: Ord,
{
let mut aggregates: BTreeMap<G, Arc<str>> = BTreeMap::new();
let mut is_consistent = true;
for k in self.keys() {
let g = f(k);
if is_consistent {
is_consistent = match aggregates.get(&g) {
Some(code_line) => code_line.as_ref() == k.code_line(),
None => {
aggregates.insert(g, k.code_line.clone());
true
}
};
}
}
is_consistent
}
fn id_to_span_group(&self) -> BTreeMap<String, SpanGroup> {
self.keys()
.map(|k| (k.id().to_owned(), k.clone()))
.collect()
}
pub fn span_group_to_parent(&self) -> BTreeMap<SpanGroup, Option<SpanGroup>> {
let id_to_sg = self.id_to_span_group();
self.keys()
.map(|sg| {
let parent = sg.parent_id().map(|pid| {
id_to_sg
.get(pid)
.expect("`id_to_sg` must have key `pid` by construction")
.clone()
});
(sg.clone(), parent)
})
.collect()
}
}