use std::sync::Arc;
use std::sync::atomic::Ordering;
use opentelemetry::trace::{SpanBuilder, Tracer};
use opentelemetry::{Context, KeyValue};
mod context;
mod helpers;
mod slots;
mod tracer;
use crate::instrumentation::context::{Envelope, InProcessParents};
use crate::instrumentation::helpers::{BitOp, apply, slot_names};
use crate::instrumentation::slots::{SLOTS, update_slots};
use crate::instrumentation::tracer::DynTracer;
use crate::registry::REGISTRY;
use crate::selector::{self, Selection, UnknownKeys};
use helpers::bit;
pub const MAX_INSTRUMENTATIONS: u32 = 64;
const _: () = assert!(
MAX_INSTRUMENTATIONS as usize == u64::BITS as usize,
"MAX_INSTRUMENTATIONS must equal the number of bits in a site's mask"
);
fn span_builder(name: &'static str, attrs: &[KeyValue]) -> SpanBuilder {
if attrs.is_empty() {
SpanBuilder::from_name(name)
} else {
SpanBuilder::from_name(name).with_attributes(attrs.to_vec())
}
}
#[doc(hidden)]
pub fn start_spans(
enabled_slots: u64,
span_name: &'static str,
attrs: Vec<KeyValue>,
) -> Option<Context> {
let slots = SLOTS.load_full();
let mut cx = Context::current();
let mut created_any = false;
let distributed = slots
.distributed
.filter(|slot| enabled_slots & bit(*slot) != 0);
if let Some(tracer) = distributed.and_then(|slot| slots.tracers[slot as usize].as_ref()) {
cx = tracer.start_in(span_builder(span_name, &attrs), &cx);
created_any = true;
}
let in_process_mask = enabled_slots & !distributed.map_or(0, bit);
if in_process_mask != 0 {
let mut envelope: Envelope = cx
.get::<InProcessParents>()
.map(|s| s.0.clone())
.unwrap_or_default();
let mut env_changed = false;
let mut bits = in_process_mask;
while bits != 0 {
let slot = bits.trailing_zeros() as u8;
bits &= bits - 1;
let Some(tracer) = slots.tracers[slot as usize].as_ref() else {
continue;
};
let parent = envelope.iter().find(|(s, _)| *s == slot).map(|(_, c)| c);
let base = parent.cloned().unwrap_or_default();
let child = tracer.start_in(span_builder(span_name, &attrs), &base);
match envelope.iter_mut().find(|(s, _)| *s == slot) {
Some(entry) => entry.1 = child,
None => envelope.push((slot, child)),
}
env_changed = true;
}
if env_changed {
cx = cx.with_value(InProcessParents(envelope));
created_any = true;
}
}
created_any.then_some(cx)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BuildError {
SlotsExhausted,
DistributedAlreadyLive,
}
impl std::fmt::Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SlotsExhausted => write!(
f,
"no free instrumentation slot ({MAX_INSTRUMENTATIONS} reached)"
),
Self::DistributedAlreadyLive => {
write!(f, "a distributed instrumentation is already configured")
}
}
}
}
impl std::error::Error for BuildError {}
#[derive(Debug)]
pub struct Instrumentation {
slot_number: u8,
}
#[derive(Default)]
pub struct InstrumentationBuilder {
tracer: Option<Arc<dyn DynTracer>>,
distributed: bool,
}
impl std::fmt::Debug for InstrumentationBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InstrumentationBuilder")
.field("tracer", &self.tracer.as_ref().map(|_| "<tracer>"))
.field("distributed", &self.distributed)
.finish()
}
}
impl InstrumentationBuilder {
#[must_use]
pub fn tracer<T>(mut self, tracer: T) -> Self
where
T: Tracer + Send + Sync + 'static,
T::Span: Send + Sync + 'static,
{
self.tracer = Some(Arc::new(tracer));
self
}
#[must_use]
pub fn distributed(mut self) -> Self {
self.distributed = true;
self
}
pub fn build(self) -> Result<Instrumentation, BuildError> {
let tracer = self
.tracer
.expect("InstrumentationBuilder::build requires a tracer");
let slot = update_slots(|slots| {
if self.distributed && slots.distributed.is_some() {
return Err(BuildError::DistributedAlreadyLive);
}
let slot = slots.alloc().ok_or(BuildError::SlotsExhausted)?;
slots.tracers[slot as usize] = Some(tracer);
if self.distributed {
slots.distributed = Some(slot);
}
Ok(slot)
})?;
Ok(Instrumentation { slot_number: slot })
}
}
impl Instrumentation {
#[must_use]
pub fn builder() -> InstrumentationBuilder {
InstrumentationBuilder::default()
}
#[must_use]
pub fn is_distributed(&self) -> bool {
SLOTS.load().distributed == Some(self.slot_number)
}
pub fn enable_all(&self) {
let b = bit(self.slot_number);
for site in REGISTRY.iter() {
site.enabled_slots.fetch_or(b, Ordering::Relaxed);
}
}
pub fn disable_all(&self) {
let b = bit(self.slot_number);
for site in REGISTRY.iter() {
site.enabled_slots.fetch_and(!b, Ordering::Relaxed);
}
}
pub fn set_enabled<S: AsRef<str>>(&self, selectors: &[S]) -> Result<Selection, UnknownKeys> {
let selection = selector::resolve(selectors)?;
apply(&selection, self.slot_number, BitOp::Replace);
Ok(selection)
}
pub fn enable<S: AsRef<str>>(&self, selectors: &[S]) -> Result<Selection, UnknownKeys> {
let selection = selector::resolve(selectors)?;
apply(&selection, self.slot_number, BitOp::Add);
Ok(selection)
}
pub fn disable<S: AsRef<str>>(&self, selectors: &[S]) -> Result<Selection, UnknownKeys> {
let selection = selector::resolve(selectors)?;
apply(&selection, self.slot_number, BitOp::Remove);
Ok(selection)
}
pub fn enabled_names(&self) -> impl Iterator<Item = &'static str> {
slot_names(self.slot_number)
}
}
impl Drop for Instrumentation {
fn drop(&mut self) {
let b = bit(self.slot_number);
for site in REGISTRY.iter() {
site.enabled_slots.fetch_and(!b, Ordering::Relaxed);
}
let slot = self.slot_number;
let _: Result<(), ()> = update_slots(|slots| {
slots.tracers[slot as usize] = None;
if slots.distributed == Some(slot) {
slots.distributed = None;
}
slots.freed.push(slot);
Ok(())
});
}
}