use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Instant;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Span {
pub name: String,
pub duration_ms: Option<u64>,
pub parent_id: Option<usize>,
pub metadata: HashMap<String, String>,
#[serde(skip)]
pub id: usize,
#[serde(skip)]
start: Option<Instant>,
}
impl Span {
fn new(name: String, parent_id: Option<usize>, id: usize) -> Self {
Self {
name,
duration_ms: None,
parent_id,
metadata: HashMap::new(),
id,
start: Some(Instant::now()),
}
}
fn end(&mut self) {
if let Some(start) = self.start.take() {
self.duration_ms = Some(start.elapsed().as_millis() as u64);
}
}
}
#[derive(Debug, Default)]
pub struct SpanCollector {
spans: Vec<Span>,
stack: Vec<usize>, enabled: bool,
}
impl SpanCollector {
pub fn new() -> Self {
Self {
spans: Vec::new(),
stack: Vec::new(),
enabled: true,
}
}
pub fn with_enabled(enabled: bool) -> Self {
Self {
spans: Vec::new(),
stack: Vec::new(),
enabled,
}
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn start_span(&mut self, name: impl Into<String>) -> usize {
if !self.enabled {
return 0;
}
let parent_id = self.stack.last().copied();
let span_id = self.spans.len();
self.spans.push(Span::new(name.into(), parent_id, span_id));
self.stack.push(span_id);
span_id
}
pub fn end_span(&mut self) {
if !self.enabled {
return;
}
if let Some(span_id) = self.stack.pop() {
if let Some(span) = self.spans.get_mut(span_id) {
span.end();
}
}
}
pub fn end_span_by_id(&mut self, span_id: usize) {
if !self.enabled {
return;
}
if let Some(span) = self.spans.get_mut(span_id) {
span.end();
}
self.stack.retain(|&id| id != span_id);
}
pub fn add_metadata(&mut self, key: impl Into<String>, value: impl Into<String>) {
if !self.enabled {
return;
}
if let Some(&span_id) = self.stack.last() {
if let Some(span) = self.spans.get_mut(span_id) {
span.metadata.insert(key.into(), value.into());
}
}
}
pub fn spans(&self) -> &[Span] {
&self.spans
}
pub fn to_stages_json(&self) -> serde_json::Value {
let spans: Vec<serde_json::Value> = self
.spans
.iter()
.map(|s| {
serde_json::json!({
"name": s.name,
"duration_ms": s.duration_ms,
"parent_id": s.parent_id,
"metadata": s.metadata,
})
})
.collect();
serde_json::json!({ "spans": spans })
}
pub fn total_duration_ms(&self) -> u64 {
self.spans
.iter()
.filter(|s| s.parent_id.is_none())
.filter_map(|s| s.duration_ms)
.sum()
}
pub fn reset(&mut self) {
self.spans.clear();
self.stack.clear();
}
pub fn is_empty(&self) -> bool {
self.spans.is_empty()
}
}
lazy_static::lazy_static! {
static ref GLOBAL_COLLECTOR: Arc<Mutex<SpanCollector>> = Arc::new(Mutex::new(SpanCollector::with_enabled(false)));
}
pub fn init_tracing(enabled: bool) {
if let Ok(mut collector) = GLOBAL_COLLECTOR.lock() {
collector.set_enabled(enabled);
collector.reset();
}
}
pub fn start_span(name: impl Into<String>) -> usize {
GLOBAL_COLLECTOR
.lock()
.map(|mut c| c.start_span(name))
.unwrap_or(0)
}
pub fn end_span() {
if let Ok(mut collector) = GLOBAL_COLLECTOR.lock() {
collector.end_span();
}
}
pub fn end_span_by_id(id: usize) {
if let Ok(mut collector) = GLOBAL_COLLECTOR.lock() {
collector.end_span_by_id(id);
}
}
pub fn add_metadata(key: impl Into<String>, value: impl Into<String>) {
if let Ok(mut collector) = GLOBAL_COLLECTOR.lock() {
collector.add_metadata(key, value);
}
}
pub fn get_stages_json() -> serde_json::Value {
GLOBAL_COLLECTOR
.lock()
.map(|c| c.to_stages_json())
.unwrap_or(serde_json::json!({ "spans": [] }))
}
pub fn reset_tracing() {
if let Ok(mut collector) = GLOBAL_COLLECTOR.lock() {
collector.reset();
}
}
pub fn is_tracing_enabled() -> bool {
GLOBAL_COLLECTOR
.lock()
.map(|c| c.is_enabled())
.unwrap_or(false)
}
pub struct SpanGuard {
id: usize,
use_global: bool,
}
impl SpanGuard {
pub fn new(name: impl Into<String>) -> Self {
let id = start_span(name);
Self {
id,
use_global: true,
}
}
pub fn id(&self) -> usize {
self.id
}
}
impl Drop for SpanGuard {
fn drop(&mut self) {
if self.use_global {
end_span();
}
}
}
#[macro_export]
macro_rules! trace_span {
($name:expr) => {
let _guard = $crate::tracing::SpanGuard::new($name);
};
($name:expr, $($key:expr => $value:expr),+) => {
let _guard = $crate::tracing::SpanGuard::new($name);
$( $crate::tracing::add_metadata($key, $value); )+
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_span_collector() {
let mut collector = SpanCollector::new();
let _span1 = collector.start_span("parent");
collector.add_metadata("key1", "value1");
let _span2 = collector.start_span("child");
collector.add_metadata("key2", "value2");
collector.end_span();
collector.end_span();
assert_eq!(collector.spans().len(), 2);
assert!(collector.spans()[0].duration_ms.is_some());
assert!(collector.spans()[1].duration_ms.is_some());
assert_eq!(collector.spans()[1].parent_id, Some(0));
}
#[test]
fn test_disabled_collector() {
let mut collector = SpanCollector::with_enabled(false);
collector.start_span("test");
collector.add_metadata("key", "value");
collector.end_span();
assert!(collector.spans().is_empty());
}
#[test]
fn test_to_stages_json() {
let mut collector = SpanCollector::new();
collector.start_span("test");
collector.add_metadata("model", "whisper");
std::thread::sleep(std::time::Duration::from_millis(10));
collector.end_span();
let json = collector.to_stages_json();
let spans = json["spans"].as_array().unwrap();
assert_eq!(spans.len(), 1);
assert_eq!(spans[0]["name"], "test");
assert!(spans[0]["duration_ms"].as_u64().unwrap() >= 10);
}
}