use std::{
fmt::Debug,
mem::take,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
time::{Instant, SystemTime},
};
use crate::{
processor::SpanProcessor,
recorder::SpanRecorder,
schema::{DatapointId, RecordData, RecordValue, SpanData},
};
pub mod processor;
pub mod recorder;
pub mod schema;
mod global;
mod local;
pub use global::{global, init};
pub use local::{
end_threadlocal_span, get_threadlocal_span, set_threadlocal_span, start_threadlocal_span,
take_threadlocal_span,
};
pub use chronograph_macros as macros;
#[derive(Debug)]
pub struct Chronograph {
context: Arc<ChronographContext>,
next_id: AtomicU64,
global_start_instant: Instant,
}
impl Chronograph {
pub fn builder() -> ChronographBuilder {
ChronographBuilder {
context: ChronographContext {
processors: Vec::new(),
recorder: SpanRecorder::NoOp(),
sample_rate: SampleRate::All,
},
}
}
pub fn start_span(&self) -> Span {
let span_id = self.next_id.fetch_add(1, Ordering::Relaxed);
Span {
sampled: self.context.sample_rate.sample(span_id),
global_start_instant: self.global_start_instant,
context: Arc::clone(&self.context),
span_id,
start_unix_time: SystemTime::now(),
start_instant: self.global_start_instant.elapsed().as_nanos() as u64,
records: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct ChronographBuilder {
context: ChronographContext,
}
impl ChronographBuilder {
pub fn with_recorder(mut self, recorder: impl Into<SpanRecorder>) -> Self {
self.context.recorder = recorder.into();
self
}
pub fn with_processor(mut self, post_processor: SpanProcessor) -> Self {
self.context.processors.push(post_processor);
self
}
pub fn with_sample_rate(mut self, sample_rate: u64) -> Self {
self.context.sample_rate = SampleRate::from(sample_rate);
self
}
pub fn build(self) -> Chronograph {
Chronograph {
context: Arc::new(self.context),
next_id: AtomicU64::new(0),
global_start_instant: Instant::now(),
}
}
}
#[derive(Debug, Clone)]
pub struct Span {
sampled: bool,
global_start_instant: Instant,
context: Arc<ChronographContext>,
span_id: u64,
start_unix_time: SystemTime,
start_instant: u64,
records: Vec<RecordData>,
}
impl Span {
pub fn record_instant(&mut self, datapoint_id: impl Into<DatapointId>) -> &mut Self {
if self.sampled {
self.record_value(
datapoint_id,
RecordValue::Instant(self.global_start_instant.elapsed().as_nanos() as u64),
);
};
self
}
pub fn record_unix_time(&mut self, datapoint_id: impl Into<DatapointId>) -> &mut Self {
if self.sampled {
self.record_value_no_sampling(
datapoint_id,
RecordValue::UnixTime(
self.start_unix_time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as i64,
),
);
}
self
}
pub fn record_value(
&mut self,
datapoint_id: impl Into<DatapointId>,
value: impl Into<RecordValue>,
) -> &mut Self {
if self.sampled {
self.record_value_no_sampling(datapoint_id, value);
}
self
}
fn record_value_no_sampling(
&mut self,
datapoint_id: impl Into<DatapointId>,
value: impl Into<RecordValue>,
) {
self.records.push(RecordData {
datapoint_id: datapoint_id.into(),
value: value.into(),
});
}
}
impl Drop for Span {
fn drop(&mut self) {
if !self.sampled {
return;
}
let span_data = SpanData {
span_id: self.span_id,
start_unix_time: self
.start_unix_time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as i64,
start_instant: self.start_instant,
end_instant: self.global_start_instant.elapsed().as_nanos() as u64,
records: take(&mut self.records),
};
for post_processor in self.context.processors.iter() {
post_processor.post_process_span(&span_data);
}
self.context.recorder.record_span(span_data);
}
}
struct ChronographContext {
recorder: SpanRecorder,
processors: Vec<SpanProcessor>,
sample_rate: SampleRate,
}
impl Debug for ChronographContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ChronographContext")
.field("recorder", &self.recorder)
.field("sample_rate", &self.sample_rate)
.field("processors_count", &self.processors.len())
.finish()
}
}
#[derive(Debug)]
enum SampleRate {
All,
Pow2(u64),
Modulo(u64),
}
impl From<u64> for SampleRate {
fn from(value: u64) -> Self {
if value == 0 {
Self::All
} else if value.is_power_of_two() {
Self::Pow2(value)
} else {
Self::Modulo(value)
}
}
}
impl SampleRate {
pub fn sample(&self, span_id: u64) -> bool {
match self {
Self::All => true,
Self::Pow2(x) => span_id & (x - 1) == 0,
Self::Modulo(x) => span_id % x == 0,
}
}
}