llm_optimizer_processor/analyzer/traits.rs
1//! Core traits for the analyzer framework
2//!
3//! This module defines the `Analyzer` trait that all analyzers must implement,
4//! along with lifecycle management and configuration traits.
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use std::time::Duration;
9use thiserror::Error;
10
11use super::types::{AnalyzerEvent, AnalysisReport, AnalyzerStats};
12
13/// Result type for analyzer operations
14pub type AnalyzerResult<T> = Result<T, AnalyzerError>;
15
16/// Errors that can occur during analysis
17#[derive(Debug, Error, Clone, Serialize, Deserialize)]
18pub enum AnalyzerError {
19 /// Analysis failed due to invalid input
20 #[error("Invalid input: {0}")]
21 InvalidInput(String),
22
23 /// Analysis failed due to insufficient data
24 #[error("Insufficient data: {0}")]
25 InsufficientData(String),
26
27 /// Analysis failed due to configuration error
28 #[error("Configuration error: {0}")]
29 ConfigurationError(String),
30
31 /// Analysis failed due to resource exhaustion
32 #[error("Resource exhausted: {0}")]
33 ResourceExhausted(String),
34
35 /// Analysis failed due to internal error
36 #[error("Internal error: {0}")]
37 InternalError(String),
38
39 /// Analyzer is not in the correct state
40 #[error("Invalid state: {0}")]
41 InvalidState(String),
42
43 /// Timeout occurred during analysis
44 #[error("Timeout: {0}")]
45 Timeout(String),
46
47 /// Analyzer is not running
48 #[error("Analyzer is not running")]
49 NotRunning,
50
51 /// Invalid state transition attempted
52 #[error("Invalid state transition from {from} to {to}")]
53 InvalidStateTransition {
54 from: String,
55 to: String,
56 },
57}
58
59/// Lifecycle state of an analyzer
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61pub enum AnalyzerState {
62 /// Analyzer is initialized but not started
63 Initialized,
64 /// Analyzer is starting up
65 Starting,
66 /// Analyzer is running and processing events
67 Running,
68 /// Analyzer is draining remaining events before stopping
69 Draining,
70 /// Analyzer has stopped
71 Stopped,
72 /// Analyzer encountered an error
73 Failed,
74}
75
76impl AnalyzerState {
77 /// Check if the analyzer can accept new events
78 pub fn can_accept_events(&self) -> bool {
79 matches!(self, AnalyzerState::Running)
80 }
81
82 /// Check if the analyzer is in a terminal state
83 pub fn is_terminal(&self) -> bool {
84 matches!(self, AnalyzerState::Stopped | AnalyzerState::Failed)
85 }
86
87 /// Convert the state to a string representation
88 pub fn as_str(&self) -> &'static str {
89 match self {
90 AnalyzerState::Initialized => "Initialized",
91 AnalyzerState::Starting => "Starting",
92 AnalyzerState::Running => "Running",
93 AnalyzerState::Draining => "Draining",
94 AnalyzerState::Stopped => "Stopped",
95 AnalyzerState::Failed => "Failed",
96 }
97 }
98}
99
100/// Configuration for an analyzer
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct AnalyzerConfig {
103 /// Unique identifier for this analyzer instance
104 pub id: String,
105
106 /// Whether the analyzer is enabled
107 pub enabled: bool,
108
109 /// Events to filter and process (empty = all events)
110 pub event_filter: Vec<String>,
111
112 /// Window duration for sliding window analysis
113 pub window_duration: Duration,
114
115 /// Slide interval for sliding windows
116 pub slide_interval: Duration,
117
118 /// Sample rate (0.0-1.0) for high-volume events
119 pub sample_rate: f64,
120
121 /// Maximum events to buffer before backpressure
122 pub max_buffer_size: usize,
123
124 /// Maximum memory usage in bytes
125 pub max_memory_bytes: usize,
126
127 /// Analysis interval (how often to generate reports)
128 pub analysis_interval: Duration,
129
130 /// Enable detailed tracing
131 pub enable_tracing: bool,
132}
133
134impl Default for AnalyzerConfig {
135 fn default() -> Self {
136 Self {
137 id: String::new(),
138 enabled: true,
139 event_filter: Vec::new(),
140 window_duration: Duration::from_secs(300), // 5 minutes
141 slide_interval: Duration::from_secs(60), // 1 minute
142 sample_rate: 1.0, // No sampling
143 max_buffer_size: 10_000,
144 max_memory_bytes: 100 * 1024 * 1024, // 100MB
145 analysis_interval: Duration::from_secs(60), // 1 minute
146 enable_tracing: false,
147 }
148 }
149}
150
151/// Core trait that all analyzers must implement
152///
153/// This trait defines the interface for real-time analysis of LLM optimization metrics.
154/// Analyzers receive events, maintain state, perform analysis, and generate reports
155/// with insights and recommendations.
156#[async_trait]
157pub trait Analyzer: Send + Sync {
158 /// Get the analyzer's unique name
159 fn name(&self) -> &str;
160
161 /// Get the analyzer's configuration
162 fn config(&self) -> &AnalyzerConfig;
163
164 /// Get the current state of the analyzer
165 fn state(&self) -> AnalyzerState;
166
167 /// Start the analyzer
168 ///
169 /// This initializes internal state and prepares the analyzer to receive events.
170 ///
171 /// # Errors
172 ///
173 /// Returns an error if the analyzer is already running or if initialization fails.
174 async fn start(&mut self) -> AnalyzerResult<()>;
175
176 /// Stop the analyzer gracefully
177 ///
178 /// This drains any remaining events and performs cleanup.
179 ///
180 /// # Errors
181 ///
182 /// Returns an error if the analyzer is already stopped or if cleanup fails.
183 async fn stop(&mut self) -> AnalyzerResult<()>;
184
185 /// Process a single event
186 ///
187 /// This is the core analysis method that processes incoming events.
188 /// The analyzer updates its internal state and may generate insights
189 /// immediately or batch them for the next report.
190 ///
191 /// # Arguments
192 ///
193 /// * `event` - The event to process
194 ///
195 /// # Returns
196 ///
197 /// Returns `Ok(())` if the event was processed successfully.
198 ///
199 /// # Errors
200 ///
201 /// Returns an error if:
202 /// - The analyzer is not in the `Running` state
203 /// - The event is invalid
204 /// - Internal processing fails
205 async fn process_event(&mut self, event: AnalyzerEvent) -> AnalyzerResult<()>;
206
207 /// Generate a report of current analysis results
208 ///
209 /// This method returns the current state of analysis, including any insights,
210 /// recommendations, and alerts generated since the last report.
211 ///
212 /// # Returns
213 ///
214 /// Returns a report containing:
215 /// - Summary statistics
216 /// - Insights discovered
217 /// - Recommendations generated
218 /// - Alerts triggered
219 ///
220 /// # Errors
221 ///
222 /// Returns an error if report generation fails.
223 async fn generate_report(&self) -> AnalyzerResult<AnalysisReport>;
224
225 /// Get current statistics about the analyzer's operation
226 ///
227 /// This includes metrics like:
228 /// - Events processed
229 /// - Analysis duration
230 /// - Memory usage
231 /// - Error counts
232 fn get_stats(&self) -> AnalyzerStats;
233
234 /// Reset the analyzer's state
235 ///
236 /// This clears all accumulated state and resets counters.
237 /// The analyzer remains in its current lifecycle state.
238 ///
239 /// # Errors
240 ///
241 /// Returns an error if reset fails.
242 async fn reset(&mut self) -> AnalyzerResult<()>;
243
244 /// Health check for the analyzer
245 ///
246 /// Returns `Ok(())` if the analyzer is healthy and able to process events.
247 ///
248 /// # Errors
249 ///
250 /// Returns an error if:
251 /// - Memory usage exceeds limits
252 /// - Error rate is too high
253 /// - Internal state is corrupted
254 async fn health_check(&self) -> AnalyzerResult<()>;
255}
256
257/// Builder for creating analyzer configurations
258pub struct AnalyzerConfigBuilder {
259 config: AnalyzerConfig,
260}
261
262impl AnalyzerConfigBuilder {
263 /// Create a new builder with the given analyzer ID
264 pub fn new(id: impl Into<String>) -> Self {
265 Self {
266 config: AnalyzerConfig {
267 id: id.into(),
268 ..Default::default()
269 },
270 }
271 }
272
273 /// Set whether the analyzer is enabled
274 pub fn enabled(mut self, enabled: bool) -> Self {
275 self.config.enabled = enabled;
276 self
277 }
278
279 /// Set the event filter
280 pub fn event_filter(mut self, filter: Vec<String>) -> Self {
281 self.config.event_filter = filter;
282 self
283 }
284
285 /// Set the window duration
286 pub fn window_duration(mut self, duration: Duration) -> Self {
287 self.config.window_duration = duration;
288 self
289 }
290
291 /// Set the slide interval
292 pub fn slide_interval(mut self, interval: Duration) -> Self {
293 self.config.slide_interval = interval;
294 self
295 }
296
297 /// Set the sample rate
298 pub fn sample_rate(mut self, rate: f64) -> Self {
299 self.config.sample_rate = rate.clamp(0.0, 1.0);
300 self
301 }
302
303 /// Set the maximum buffer size
304 pub fn max_buffer_size(mut self, size: usize) -> Self {
305 self.config.max_buffer_size = size;
306 self
307 }
308
309 /// Set the maximum memory usage
310 pub fn max_memory_bytes(mut self, bytes: usize) -> Self {
311 self.config.max_memory_bytes = bytes;
312 self
313 }
314
315 /// Set the analysis interval
316 pub fn analysis_interval(mut self, interval: Duration) -> Self {
317 self.config.analysis_interval = interval;
318 self
319 }
320
321 /// Enable or disable detailed tracing
322 pub fn enable_tracing(mut self, enabled: bool) -> Self {
323 self.config.enable_tracing = enabled;
324 self
325 }
326
327 /// Build the configuration
328 pub fn build(self) -> AnalyzerConfig {
329 self.config
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336
337 #[test]
338 fn test_analyzer_state_can_accept_events() {
339 assert!(!AnalyzerState::Initialized.can_accept_events());
340 assert!(!AnalyzerState::Starting.can_accept_events());
341 assert!(AnalyzerState::Running.can_accept_events());
342 assert!(!AnalyzerState::Draining.can_accept_events());
343 assert!(!AnalyzerState::Stopped.can_accept_events());
344 assert!(!AnalyzerState::Failed.can_accept_events());
345 }
346
347 #[test]
348 fn test_analyzer_state_is_terminal() {
349 assert!(!AnalyzerState::Initialized.is_terminal());
350 assert!(!AnalyzerState::Starting.is_terminal());
351 assert!(!AnalyzerState::Running.is_terminal());
352 assert!(!AnalyzerState::Draining.is_terminal());
353 assert!(AnalyzerState::Stopped.is_terminal());
354 assert!(AnalyzerState::Failed.is_terminal());
355 }
356
357 #[test]
358 fn test_analyzer_config_builder() {
359 let config = AnalyzerConfigBuilder::new("test-analyzer")
360 .enabled(true)
361 .window_duration(Duration::from_secs(600))
362 .sample_rate(0.5)
363 .max_buffer_size(5000)
364 .build();
365
366 assert_eq!(config.id, "test-analyzer");
367 assert!(config.enabled);
368 assert_eq!(config.window_duration, Duration::from_secs(600));
369 assert_eq!(config.sample_rate, 0.5);
370 assert_eq!(config.max_buffer_size, 5000);
371 }
372
373 #[test]
374 fn test_sample_rate_clamping() {
375 let config = AnalyzerConfigBuilder::new("test")
376 .sample_rate(1.5) // Should be clamped to 1.0
377 .build();
378 assert_eq!(config.sample_rate, 1.0);
379
380 let config = AnalyzerConfigBuilder::new("test")
381 .sample_rate(-0.5) // Should be clamped to 0.0
382 .build();
383 assert_eq!(config.sample_rate, 0.0);
384 }
385}