Skip to main content

enya_client/tracing/
mod.rs

1//! Tracing client abstraction for distributed trace backends.
2//!
3//! This module provides a unified interface for querying traces from different
4//! backends (Grafana Tempo, Jaeger, etc.) using the TracingClient trait.
5//!
6//! # Architecture
7//!
8//! The [`TracingClient`] trait defines a promise-based async interface that all
9//! backends implement. Methods return [`Promise`] objects that can be polled
10//! each frame in immediate mode GUIs like egui.
11//!
12//! # Example
13//!
14//! ```ignore
15//! use enya_client::tracing::{TracingClient, TraceSearchParams};
16//! use enya_client::tracing::tempo::TempoClient;
17//!
18//! // Create a client for your backend
19//! let client = TempoClient::new("http://localhost:3200");
20//!
21//! // Fire off a trace query - returns a promise
22//! let promise = client.get_trace("abc123def456", &ctx);
23//!
24//! // In your update loop, poll for results
25//! if let Some(result) = promise.ready() {
26//!     match result {
27//!         Ok(trace) => { /* render waterfall */ }
28//!         Err(e) => { /* show error */ }
29//!     }
30//! }
31//! ```
32
33pub mod tempo;
34
35// Re-export common types from tempo (these are backend-agnostic)
36pub use poll_promise::Promise;
37pub use tempo::types::{
38    Span, SpanLog, SpanStatus, Trace, TraceId, TraceSearchParams, TraceSummary, format_duration_us,
39};
40
41use crate::error::ClientError;
42
43/// Result type for trace fetch operations.
44pub type TraceResult = Result<Trace, ClientError>;
45
46/// Result type for trace search operations.
47pub type SearchResult = Result<Vec<TraceSummary>, ClientError>;
48
49/// Tracing client trait - promise-based async interface.
50///
51/// Implementations handle the HTTP communication with the backend. All async methods
52/// return [`Promise`] objects that can be polled each frame.
53pub trait TracingClient {
54    /// Fetch a trace by its ID (non-blocking).
55    ///
56    /// Returns a promise that resolves to the full trace with all spans.
57    /// The `egui::Context` is used to request a repaint when the response is ready.
58    fn get_trace(&self, trace_id: &str, ctx: &egui::Context) -> Promise<TraceResult>;
59
60    /// Search for traces matching the given parameters (non-blocking).
61    ///
62    /// Returns a promise that resolves to a list of trace summaries.
63    fn search_traces(
64        &self,
65        params: TraceSearchParams,
66        ctx: &egui::Context,
67    ) -> Promise<SearchResult>;
68
69    /// Get the backend type identifier (e.g., "tempo", "jaeger").
70    fn backend_type(&self) -> &'static str;
71}
72
73/// Manages in-flight trace fetch requests using promises.
74///
75/// Similar to [`QueryManager`](crate::QueryManager) for metrics, but for trace operations.
76pub struct TraceManager {
77    /// Pending trace fetch promise.
78    trace_promise: Option<Promise<TraceResult>>,
79    /// Pending search promise.
80    search_promise: Option<Promise<SearchResult>>,
81}
82
83impl Default for TraceManager {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl TraceManager {
90    /// Create a new trace manager.
91    #[must_use]
92    pub fn new() -> Self {
93        Self {
94            trace_promise: None,
95            search_promise: None,
96        }
97    }
98
99    /// Check if a trace fetch is in flight.
100    #[must_use]
101    pub fn is_fetching_trace(&self) -> bool {
102        self.trace_promise.is_some()
103    }
104
105    /// Check if a search is in flight.
106    #[must_use]
107    pub fn is_searching(&self) -> bool {
108        self.search_promise.is_some()
109    }
110
111    /// Fetch a trace by ID.
112    ///
113    /// If a fetch is already in flight, it is cancelled and replaced.
114    pub fn fetch_trace<C: TracingClient + ?Sized>(
115        &mut self,
116        client: &C,
117        trace_id: &str,
118        ctx: &egui::Context,
119    ) {
120        self.trace_promise = Some(client.get_trace(trace_id, ctx));
121    }
122
123    /// Search for traces.
124    ///
125    /// If a search is already in flight, it is cancelled and replaced.
126    pub fn search<C: TracingClient + ?Sized>(
127        &mut self,
128        client: &C,
129        params: TraceSearchParams,
130        ctx: &egui::Context,
131    ) {
132        self.search_promise = Some(client.search_traces(params, ctx));
133    }
134
135    /// Poll for trace fetch result.
136    ///
137    /// Returns `Some(result)` if a fetch just completed, `None` otherwise.
138    pub fn poll_trace(&mut self) -> Option<TraceResult> {
139        let promise = self.trace_promise.as_ref()?;
140        if let Some(result) = promise.ready() {
141            let result = result.clone();
142            self.trace_promise = None;
143            Some(result)
144        } else {
145            None
146        }
147    }
148
149    /// Poll for search result.
150    ///
151    /// Returns `Some(result)` if a search just completed, `None` otherwise.
152    pub fn poll_search(&mut self) -> Option<SearchResult> {
153        let promise = self.search_promise.as_ref()?;
154        if let Some(result) = promise.ready() {
155            let result = result.clone();
156            self.search_promise = None;
157            Some(result)
158        } else {
159            None
160        }
161    }
162
163    /// Cancel any pending trace fetch.
164    pub fn cancel_trace(&mut self) {
165        self.trace_promise = None;
166    }
167
168    /// Cancel any pending search.
169    pub fn cancel_search(&mut self) {
170        self.search_promise = None;
171    }
172
173    /// Cancel all pending operations.
174    pub fn cancel_all(&mut self) {
175        self.trace_promise = None;
176        self.search_promise = None;
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_trace_manager_initial_state() {
186        let manager = TraceManager::new();
187        assert!(!manager.is_fetching_trace());
188        assert!(!manager.is_searching());
189    }
190}