1use std::borrow::Cow;
9use std::sync::atomic::{AtomicUsize, Ordering};
10
11#[derive(Debug, Clone)]
13pub struct SpanPoolConfig {
14 pub capacity: usize,
16 pub enabled: bool,
18}
19
20impl Default for SpanPoolConfig {
21 fn default() -> Self {
22 SpanPoolConfig {
23 capacity: 1024,
24 enabled: true,
25 }
26 }
27}
28
29impl SpanPoolConfig {
30 pub fn new(capacity: usize) -> Self {
32 SpanPoolConfig {
33 capacity,
34 enabled: true,
35 }
36 }
37
38 pub fn disabled() -> Self {
40 SpanPoolConfig {
41 capacity: 0,
42 enabled: false,
43 }
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct PooledSpan {
50 pub trace_id: String,
51 pub span_id: String,
52 pub name: Cow<'static, str>,
55 pub attributes: Vec<(Cow<'static, str>, String)>,
57 pub timestamp_nanos: u64,
58 pub duration_nanos: u64,
59 pub status_code: i32, }
61
62impl PooledSpan {
63 fn new() -> Self {
65 PooledSpan {
66 trace_id: String::new(),
67 span_id: String::new(),
68 name: Cow::Borrowed(""),
69 attributes: Vec::new(),
70 timestamp_nanos: 0,
71 duration_nanos: 0,
72 status_code: 0,
73 }
74 }
75
76 fn reset(&mut self) {
78 self.trace_id.clear();
79 self.span_id.clear();
80 self.name = Cow::Borrowed("");
81 self.attributes.clear();
82 self.timestamp_nanos = 0;
83 self.duration_nanos = 0;
84 self.status_code = 0;
85 }
86
87 pub fn set_name_static(&mut self, name: &'static str) {
89 self.name = Cow::Borrowed(name);
90 }
91
92 pub fn set_name_owned(&mut self, name: String) {
94 self.name = Cow::Owned(name);
95 }
96
97 pub fn add_attribute_static(&mut self, key: &'static str, value: String) {
99 self.attributes.push((Cow::Borrowed(key), value));
100 }
101
102 pub fn add_attribute_owned(&mut self, key: String, value: String) {
104 self.attributes.push((Cow::Owned(key), value));
105 }
106}
107
108pub struct SpanPool {
110 pool: Vec<PooledSpan>,
111 config: SpanPoolConfig,
112 allocated: AtomicUsize,
113 acquired: AtomicUsize,
114}
115
116impl SpanPool {
117 pub fn new(config: SpanPoolConfig) -> Self {
119 let capacity = config.capacity;
120 let mut pool = Vec::with_capacity(capacity);
121
122 if config.enabled {
123 for _ in 0..capacity {
125 pool.push(PooledSpan::new());
126 }
127 }
128
129 SpanPool {
130 pool,
131 config,
132 allocated: AtomicUsize::new(capacity),
133 acquired: AtomicUsize::new(0),
134 }
135 }
136
137 pub fn acquire(&mut self) -> PooledSpan {
142 if !self.config.enabled {
143 self.allocated.fetch_add(1, Ordering::Relaxed);
145 return PooledSpan::new();
146 }
147
148 self.acquired.fetch_add(1, Ordering::Relaxed);
149
150 if let Some(mut span) = self.pool.pop() {
151 span.reset();
152 span
153 } else {
154 self.allocated.fetch_add(1, Ordering::Relaxed);
156 PooledSpan::new()
157 }
158 }
159
160 pub fn release(&mut self, span: PooledSpan) {
164 if !self.config.enabled {
165 return;
167 }
168
169 if self.pool.len() < self.pool.capacity() {
171 self.pool.push(span);
172 }
173 }
175
176 pub fn stats(&self) -> PoolStats {
178 PoolStats {
179 capacity: self.config.capacity,
180 available: self.pool.len(),
181 allocated: self.allocated.load(Ordering::Relaxed),
182 acquired: self.acquired.load(Ordering::Relaxed),
183 enabled: self.config.enabled,
184 }
185 }
186
187 pub fn is_enabled(&self) -> bool {
189 self.config.enabled
190 }
191
192 pub fn available(&self) -> usize {
194 self.pool.len()
195 }
196
197 pub fn capacity(&self) -> usize {
199 self.config.capacity
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct PoolStats {
206 pub capacity: usize,
208 pub available: usize,
210 pub allocated: usize,
212 pub acquired: usize,
214 pub enabled: bool,
216}
217
218impl PoolStats {
219 pub fn hit_rate(&self) -> f64 {
221 if self.acquired == 0 {
222 return 0.0;
223 }
224 let hits = self.acquired.saturating_sub(self.allocated - self.capacity);
225 (hits as f64 / self.acquired as f64) * 100.0
226 }
227
228 pub fn utilization(&self) -> f64 {
230 if self.capacity == 0 {
231 return 0.0;
232 }
233 let used = self.capacity - self.available;
234 (used as f64 / self.capacity as f64) * 100.0
235 }
236}
237
238static_assertions::assert_impl_all!(SpanPoolConfig: Send, Sync);
240static_assertions::assert_impl_all!(PooledSpan: Send, Sync);
241static_assertions::assert_impl_all!(PoolStats: Send, Sync);
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_pool_acquire_release() {
249 let mut pool = SpanPool::new(SpanPoolConfig::new(10));
250
251 let span = pool.acquire();
253 assert_eq!(pool.available(), 9);
254
255 pool.release(span);
257 assert_eq!(pool.available(), 10);
258 }
259
260 #[test]
261 fn test_pool_exhaustion() {
262 let mut pool = SpanPool::new(SpanPoolConfig::new(2));
263
264 let span1 = pool.acquire();
266 let span2 = pool.acquire();
267 assert_eq!(pool.available(), 0);
268
269 let span3 = pool.acquire();
271 assert_eq!(pool.available(), 0);
272
273 pool.release(span1);
275 pool.release(span2);
276 pool.release(span3);
277 assert_eq!(pool.available(), 2); }
279
280 #[test]
281 fn test_pool_reset() {
282 let mut pool = SpanPool::new(SpanPoolConfig::new(10));
283
284 let mut span = pool.acquire();
286 span.set_name_owned("test".to_string());
287 span.trace_id = "abc123".to_string();
288 span.timestamp_nanos = 12345;
289
290 pool.release(span);
292 let span2 = pool.acquire();
293
294 assert_eq!(span2.name.as_ref(), "");
296 assert_eq!(span2.trace_id, "");
297 assert_eq!(span2.timestamp_nanos, 0);
298 }
299
300 #[test]
301 fn test_zero_copy_static_strings() {
302 let mut pool = SpanPool::new(SpanPoolConfig::new(10));
303
304 let mut span = pool.acquire();
306 span.set_name_static("syscall:open");
307 span.add_attribute_static("syscall.name", "open".to_string());
308 span.add_attribute_static("syscall.result", "3".to_string());
309
310 assert_eq!(span.name.as_ref(), "syscall:open");
312 assert!(matches!(span.name, Cow::Borrowed(_)));
313 assert_eq!(span.attributes.len(), 2);
314 assert!(matches!(span.attributes[0].0, Cow::Borrowed(_)));
315 assert!(matches!(span.attributes[1].0, Cow::Borrowed(_)));
316
317 let mut span2 = pool.acquire();
319 span2.set_name_owned("syscall:open".to_string());
320 span2.add_attribute_owned("syscall.name".to_string(), "open".to_string());
321
322 assert_eq!(span2.name.as_ref(), "syscall:open");
323 assert!(matches!(span2.name, Cow::Owned(_)));
324 assert!(matches!(span2.attributes[0].0, Cow::Owned(_)));
325
326 pool.release(span);
327 pool.release(span2);
328 }
329
330 #[test]
331 fn test_pool_disabled() {
332 let mut pool = SpanPool::new(SpanPoolConfig::disabled());
333 assert!(!pool.is_enabled());
334 assert_eq!(pool.capacity(), 0);
335
336 let span1 = pool.acquire();
338 let span2 = pool.acquire();
339
340 pool.release(span1);
342 pool.release(span2);
343 assert_eq!(pool.available(), 0);
344 }
345
346 #[test]
347 fn test_pool_stats() {
348 let mut pool = SpanPool::new(SpanPoolConfig::new(10));
349
350 let stats = pool.stats();
352 assert_eq!(stats.capacity, 10);
353 assert_eq!(stats.available, 10);
354 assert_eq!(stats.allocated, 10);
355 assert_eq!(stats.acquired, 0);
356
357 let span1 = pool.acquire();
359 let span2 = pool.acquire();
360
361 let stats = pool.stats();
362 assert_eq!(stats.available, 8);
363 assert_eq!(stats.acquired, 2);
364
365 pool.release(span1);
367 let stats = pool.stats();
368 assert_eq!(stats.available, 9);
369
370 drop(span2);
372 }
373
374 #[test]
375 fn test_pool_hit_rate() {
376 let mut pool = SpanPool::new(SpanPoolConfig::new(2));
377
378 let span1 = pool.acquire(); let span2 = pool.acquire(); pool.release(span1);
382 pool.release(span2);
383
384 let stats = pool.stats();
385 assert!(stats.hit_rate() >= 99.0); let span1 = pool.acquire();
389 let span2 = pool.acquire();
390 let _span3 = pool.acquire(); let stats = pool.stats();
393 assert!(stats.hit_rate() < 100.0); drop((span1, span2));
396 }
397
398 #[test]
399 fn test_pool_utilization() {
400 let mut pool = SpanPool::new(SpanPoolConfig::new(10));
401
402 let stats = pool.stats();
404 assert_eq!(stats.utilization(), 0.0);
405
406 let mut spans = Vec::new();
408 for _ in 0..5 {
409 spans.push(pool.acquire());
410 }
411
412 let stats = pool.stats();
413 assert_eq!(stats.utilization(), 50.0);
414
415 for _ in 0..5 {
417 spans.push(pool.acquire());
418 }
419
420 let stats = pool.stats();
421 assert_eq!(stats.utilization(), 100.0);
422
423 for span in spans {
425 pool.release(span);
426 }
427 }
428
429 #[test]
430 fn test_pool_concurrent_usage() {
431 let mut pool = SpanPool::new(SpanPoolConfig::new(100));
432
433 for _ in 0..1000 {
435 let span = pool.acquire();
436 pool.release(span);
437 }
438
439 let stats = pool.stats();
440 assert_eq!(stats.acquired, 1000);
441 assert_eq!(stats.available, 100); }
443
444 #[test]
445 fn test_pool_growth() {
446 let mut pool = SpanPool::new(SpanPoolConfig::new(10));
447
448 let mut spans = Vec::new();
450 for _ in 0..20 {
451 spans.push(pool.acquire());
452 }
453
454 let stats = pool.stats();
455 assert_eq!(stats.acquired, 20);
456 assert!(stats.allocated >= 20); for span in spans {
460 pool.release(span);
461 }
462
463 assert_eq!(pool.available(), 10);
465 }
466}