use std::backtrace::Backtrace;
use std::fmt::Display;
use std::sync::{Arc, Mutex};
use biometrics::{Collector, Counter};
use buffertk::stack_pack;
use utilz::stopwatch::Stopwatch;
use prototk::field_types::*;
use prototk::{FieldNumber, FieldPackHelper, FieldType};
use rpc_pb::TraceID;
pub const LABEL_FIELD_NUMBER: u32 = prototk::LAST_FIELD_NUMBER;
pub const TRACE_ID_FIELD_NUMBER: u32 = prototk::LAST_FIELD_NUMBER - 1;
pub const BACKTRACE_FIELD_NUMBER: u32 = prototk::LAST_FIELD_NUMBER - 8;
pub const STOPWATCH_FIELD_NUMBER: u32 = prototk::LAST_FIELD_NUMBER - 9;
static TRACE_INSTANTIATIONS: Counter = Counter::new("indicio.trace.instantiation");
static TRACE_ID_GENERATE_FAILED: Counter = Counter::new("indicio.trace.id_generate_failed");
static TRACE_NOT_SAMPLED: Counter = Counter::new("indicio.trace.not_sampled");
static TRACE_DROPPED: Counter = Counter::new("indicio.trace.dropped");
static TRACE_EMIT: Counter = Counter::new("indicio.trace.emit");
static TRACE_FLUSH: Counter = Counter::new("indicio.trace.flush");
static TRACE_WITH_VALUE: Counter = Counter::new("indicio.trace.with_value");
static TRACE_WITH_BACKTRACE: Counter = Counter::new("indicio.trace.with_backtrace");
static TRACE_WITH_STOPWATCH: Counter = Counter::new("indicio.trace.with_stopwatch");
pub fn register_biometrics(collector: &mut Collector) {
if !collector.ingest_swizzle(module_path!(), file!(), line!()) {
return;
}
collector.register_counter(&TRACE_INSTANTIATIONS);
collector.register_counter(&TRACE_ID_GENERATE_FAILED);
collector.register_counter(&TRACE_NOT_SAMPLED);
collector.register_counter(&TRACE_DROPPED);
collector.register_counter(&TRACE_EMIT);
collector.register_counter(&TRACE_FLUSH);
collector.register_counter(&TRACE_WITH_VALUE);
collector.register_counter(&TRACE_WITH_BACKTRACE);
collector.register_counter(&TRACE_WITH_STOPWATCH);
}
pub struct Trace {
id: Option<TraceID>,
stopwatch: Option<Stopwatch>,
value: Vec<u8>,
}
impl Trace {
pub fn new(label: &str) -> Self {
TRACE_INSTANTIATIONS.click();
let mut id = None;
TRACER.with(|t| {
id = t.lock().unwrap().start();
});
let trace = Self {
id,
stopwatch: None,
value: Vec::new(),
};
let trace = trace.with_value::<string, LABEL_FIELD_NUMBER>(label);
if let Some(trace_id) = id {
trace.with_value::<string, TRACE_ID_FIELD_NUMBER>(&trace_id.human_readable())
} else {
trace
}
}
pub fn with_value<'a, F: FieldType<'a>, const N: u32>(
mut self,
field_value: F::Native,
) -> Self
where
F: FieldType<'a> + 'a,
F::Native: Clone + Display + FieldPackHelper<'a, F> + 'a,
{
if self.id.is_some() {
TRACE_WITH_VALUE.click();
stack_pack(F::field_packer(FieldNumber::must(N), &field_value)).append_to_vec(&mut self.value);
}
self
}
pub fn with_backtrace(self) -> Self {
if self.id.is_some() {
return self;
}
TRACE_WITH_BACKTRACE.click();
let backtrace = format!("{}", Backtrace::force_capture());
self.with_value::<string, BACKTRACE_FIELD_NUMBER>(&backtrace)
}
pub fn with_stopwatch(mut self) -> Self {
if self.id.is_none() {
return self;
}
TRACE_WITH_STOPWATCH.click();
self.stopwatch = Some(Stopwatch::default());
self
}
pub fn finish(mut self) {
if let Some(stopwatch) = &self.stopwatch {
let time_ms: f64 = stopwatch.since();
self = self.with_value::<double, STOPWATCH_FIELD_NUMBER>(time_ms);
}
TRACER.with(|t| {
t.lock().unwrap().finish(self);
});
}
pub fn panic<S: AsRef<str>>(self, message: S) -> ! {
self.finish();
TRACER.with(|t| {
t.lock().unwrap().flush();
});
panic!("{}\n", message.as_ref());
}
}
thread_local! {
static TRACER: Arc<Mutex<Tracer>> = Arc::new(Mutex::new(Tracer::new()));
}
struct Tracer {
emitter: Option<Arc<dyn Emitter>>,
}
impl Tracer {
pub fn start(&mut self) -> Option<TraceID> {
match TraceID::generate() {
Some(id) => Some(id),
None => {
TRACE_ID_GENERATE_FAILED.click();
Some(TraceID::default())
}
}
}
pub fn finish(&mut self, trace: Trace) {
if trace.id.is_none() {
TRACE_NOT_SAMPLED.click();
return;
}
let emitter = match &self.emitter {
Some(e) => e,
None => {
TRACE_DROPPED.click();
return;
}
};
TRACE_EMIT.click();
emitter.emit(trace);
}
pub fn flush(&mut self) {
let emitter = match &self.emitter {
Some(e) => e,
None => {
TRACE_DROPPED.click();
return;
}
};
TRACE_FLUSH.click();
emitter.flush();
}
const fn new() -> Self {
Self { emitter: None }
}
fn set_emitter(&mut self, emitter: Arc<dyn Emitter>) {
self.emitter = Some(emitter);
}
}
pub trait Emitter {
fn emit(&self, trace: Trace);
fn flush(&self);
}
pub fn register_emitter<E: Emitter + 'static>(emitter: E) {
TRACER.with(|t| {
t.lock().unwrap().set_emitter(Arc::new(emitter));
});
}