use std::borrow::Cow;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug, Clone)]
pub struct SpanPoolConfig {
pub capacity: usize,
pub enabled: bool,
}
impl Default for SpanPoolConfig {
fn default() -> Self {
SpanPoolConfig { capacity: 1024, enabled: true }
}
}
impl SpanPoolConfig {
pub fn new(capacity: usize) -> Self {
SpanPoolConfig { capacity, enabled: true }
}
pub fn disabled() -> Self {
SpanPoolConfig { capacity: 0, enabled: false }
}
}
#[derive(Debug, Clone)]
pub struct PooledSpan {
pub trace_id: String,
pub span_id: String,
pub name: Cow<'static, str>,
pub attributes: Vec<(Cow<'static, str>, String)>,
pub timestamp_nanos: u64,
pub duration_nanos: u64,
pub status_code: i32, }
impl PooledSpan {
fn new() -> Self {
PooledSpan {
trace_id: String::new(),
span_id: String::new(),
name: Cow::Borrowed(""),
attributes: Vec::new(),
timestamp_nanos: 0,
duration_nanos: 0,
status_code: 0,
}
}
fn reset(&mut self) {
self.trace_id.clear();
self.span_id.clear();
self.name = Cow::Borrowed("");
self.attributes.clear();
self.timestamp_nanos = 0;
self.duration_nanos = 0;
self.status_code = 0;
}
pub fn set_name_static(&mut self, name: &'static str) {
self.name = Cow::Borrowed(name);
}
pub fn set_name_owned(&mut self, name: String) {
self.name = Cow::Owned(name);
}
pub fn add_attribute_static(&mut self, key: &'static str, value: String) {
self.attributes.push((Cow::Borrowed(key), value));
}
pub fn add_attribute_owned(&mut self, key: String, value: String) {
self.attributes.push((Cow::Owned(key), value));
}
}
pub struct SpanPool {
pool: Vec<PooledSpan>,
config: SpanPoolConfig,
allocated: AtomicUsize,
acquired: AtomicUsize,
}
impl SpanPool {
pub fn new(config: SpanPoolConfig) -> Self {
let capacity = config.capacity;
let mut pool = Vec::with_capacity(capacity);
if config.enabled {
for _ in 0..capacity {
pool.push(PooledSpan::new());
}
}
SpanPool {
pool,
config,
allocated: AtomicUsize::new(capacity),
acquired: AtomicUsize::new(0),
}
}
pub fn acquire(&mut self) -> PooledSpan {
if !self.config.enabled {
self.allocated.fetch_add(1, Ordering::Relaxed);
return PooledSpan::new();
}
self.acquired.fetch_add(1, Ordering::Relaxed);
if let Some(mut span) = self.pool.pop() {
span.reset();
span
} else {
self.allocated.fetch_add(1, Ordering::Relaxed);
PooledSpan::new()
}
}
pub fn release(&mut self, span: PooledSpan) {
if !self.config.enabled {
return;
}
if self.pool.len() < self.pool.capacity() {
self.pool.push(span);
}
}
pub fn stats(&self) -> PoolStats {
PoolStats {
capacity: self.config.capacity,
available: self.pool.len(),
allocated: self.allocated.load(Ordering::Relaxed),
acquired: self.acquired.load(Ordering::Relaxed),
enabled: self.config.enabled,
}
}
pub fn is_enabled(&self) -> bool {
self.config.enabled
}
pub fn available(&self) -> usize {
self.pool.len()
}
pub fn capacity(&self) -> usize {
self.config.capacity
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub capacity: usize,
pub available: usize,
pub allocated: usize,
pub acquired: usize,
pub enabled: bool,
}
impl PoolStats {
pub fn hit_rate(&self) -> f64 {
if self.acquired == 0 {
return 0.0;
}
let hits = self.acquired.saturating_sub(self.allocated - self.capacity);
(hits as f64 / self.acquired as f64) * 100.0
}
pub fn utilization(&self) -> f64 {
if self.capacity == 0 {
return 0.0;
}
let used = self.capacity - self.available;
(used as f64 / self.capacity as f64) * 100.0
}
}
static_assertions::assert_impl_all!(SpanPoolConfig: Send, Sync);
static_assertions::assert_impl_all!(PooledSpan: Send, Sync);
static_assertions::assert_impl_all!(PoolStats: Send, Sync);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_acquire_release() {
let mut pool = SpanPool::new(SpanPoolConfig::new(10));
let span = pool.acquire();
assert_eq!(pool.available(), 9);
pool.release(span);
assert_eq!(pool.available(), 10);
}
#[test]
fn test_pool_exhaustion() {
let mut pool = SpanPool::new(SpanPoolConfig::new(2));
let span1 = pool.acquire();
let span2 = pool.acquire();
assert_eq!(pool.available(), 0);
let span3 = pool.acquire();
assert_eq!(pool.available(), 0);
pool.release(span1);
pool.release(span2);
pool.release(span3);
assert_eq!(pool.available(), 2); }
#[test]
fn test_pool_reset() {
let mut pool = SpanPool::new(SpanPoolConfig::new(10));
let mut span = pool.acquire();
span.set_name_owned("test".to_string());
span.trace_id = "abc123".to_string();
span.timestamp_nanos = 12345;
pool.release(span);
let span2 = pool.acquire();
assert_eq!(span2.name.as_ref(), "");
assert_eq!(span2.trace_id, "");
assert_eq!(span2.timestamp_nanos, 0);
}
#[test]
fn test_zero_copy_static_strings() {
let mut pool = SpanPool::new(SpanPoolConfig::new(10));
let mut span = pool.acquire();
span.set_name_static("syscall:open");
span.add_attribute_static("syscall.name", "open".to_string());
span.add_attribute_static("syscall.result", "3".to_string());
assert_eq!(span.name.as_ref(), "syscall:open");
assert!(matches!(span.name, Cow::Borrowed(_)));
assert_eq!(span.attributes.len(), 2);
assert!(matches!(span.attributes[0].0, Cow::Borrowed(_)));
assert!(matches!(span.attributes[1].0, Cow::Borrowed(_)));
let mut span2 = pool.acquire();
span2.set_name_owned("syscall:open".to_string());
span2.add_attribute_owned("syscall.name".to_string(), "open".to_string());
assert_eq!(span2.name.as_ref(), "syscall:open");
assert!(matches!(span2.name, Cow::Owned(_)));
assert!(matches!(span2.attributes[0].0, Cow::Owned(_)));
pool.release(span);
pool.release(span2);
}
#[test]
fn test_pool_disabled() {
let mut pool = SpanPool::new(SpanPoolConfig::disabled());
assert!(!pool.is_enabled());
assert_eq!(pool.capacity(), 0);
let span1 = pool.acquire();
let span2 = pool.acquire();
pool.release(span1);
pool.release(span2);
assert_eq!(pool.available(), 0);
}
#[test]
fn test_pool_stats() {
let mut pool = SpanPool::new(SpanPoolConfig::new(10));
let stats = pool.stats();
assert_eq!(stats.capacity, 10);
assert_eq!(stats.available, 10);
assert_eq!(stats.allocated, 10);
assert_eq!(stats.acquired, 0);
let span1 = pool.acquire();
let span2 = pool.acquire();
let stats = pool.stats();
assert_eq!(stats.available, 8);
assert_eq!(stats.acquired, 2);
pool.release(span1);
let stats = pool.stats();
assert_eq!(stats.available, 9);
drop(span2);
}
#[test]
fn test_pool_hit_rate() {
let mut pool = SpanPool::new(SpanPoolConfig::new(2));
let span1 = pool.acquire(); let span2 = pool.acquire(); pool.release(span1);
pool.release(span2);
let stats = pool.stats();
assert!(stats.hit_rate() >= 99.0);
let span1 = pool.acquire();
let span2 = pool.acquire();
let _span3 = pool.acquire();
let stats = pool.stats();
assert!(stats.hit_rate() < 100.0);
drop((span1, span2));
}
#[test]
fn test_pool_utilization() {
let mut pool = SpanPool::new(SpanPoolConfig::new(10));
let stats = pool.stats();
assert_eq!(stats.utilization(), 0.0);
let mut spans = Vec::new();
for _ in 0..5 {
spans.push(pool.acquire());
}
let stats = pool.stats();
assert_eq!(stats.utilization(), 50.0);
for _ in 0..5 {
spans.push(pool.acquire());
}
let stats = pool.stats();
assert_eq!(stats.utilization(), 100.0);
for span in spans {
pool.release(span);
}
}
#[test]
fn test_pool_concurrent_usage() {
let mut pool = SpanPool::new(SpanPoolConfig::new(100));
for _ in 0..1000 {
let span = pool.acquire();
pool.release(span);
}
let stats = pool.stats();
assert_eq!(stats.acquired, 1000);
assert_eq!(stats.available, 100); }
#[test]
fn test_pool_growth() {
let mut pool = SpanPool::new(SpanPoolConfig::new(10));
let mut spans = Vec::new();
for _ in 0..20 {
spans.push(pool.acquire());
}
let stats = pool.stats();
assert_eq!(stats.acquired, 20);
assert!(stats.allocated >= 20);
for span in spans {
pool.release(span);
}
assert_eq!(pool.available(), 10);
}
}