Skip to main content

llm_optimizer_processor/metrics/
prometheus.rs

1//! Prometheus metrics definitions for the Stream Processor
2//!
3//! This module defines all metrics exported by the processor, following
4//! Prometheus naming conventions and best practices.
5
6use super::labels::{
7    BackendLabel, CacheLayerLabel, LabelNames, OperationLabel, ResultLabel, StrategyLabel,
8    WindowTypeLabel,
9};
10use super::registry::MetricsRegistry;
11use prometheus_client::metrics::counter::Counter;
12use prometheus_client::metrics::family::Family;
13use prometheus_client::metrics::gauge::Gauge;
14use prometheus_client::metrics::histogram::{exponential_buckets, Histogram};
15use prometheus_client::registry::Registry;
16use std::sync::atomic::{AtomicI64, AtomicU64};
17use std::sync::Arc;
18
19/// Comprehensive metrics for the Stream Processor
20pub struct ProcessorMetrics {
21    // === Stream Processing Metrics ===
22    /// Total number of events received by the processor
23    pub events_received_total: Counter<u64, AtomicU64>,
24
25    /// Total number of events processed, labeled by result (success/error/dropped/filtered)
26    pub events_processed_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
27
28    /// Duration of event processing in seconds
29    pub event_processing_duration_seconds: Family<Vec<(String, String)>, Histogram>,
30
31    /// Current pipeline lag in seconds
32    pub pipeline_lag_seconds: Gauge<f64, AtomicU64>,
33
34    /// Number of backpressure events (queued events)
35    pub backpressure_events: Gauge<i64, AtomicI64>,
36
37    // === Window Metrics ===
38    /// Total number of windows created
39    pub windows_created_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
40
41    /// Total number of windows triggered
42    pub windows_triggered_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
43
44    /// Number of currently active windows
45    pub windows_active: Family<Vec<(String, String)>, Gauge<i64, AtomicI64>>,
46
47    /// Distribution of window sizes (number of events)
48    pub window_size_events: Family<Vec<(String, String)>, Histogram>,
49
50    /// Total number of late events (arriving after watermark)
51    pub late_events_total: Counter<u64, AtomicU64>,
52
53    // === Aggregation Metrics ===
54    /// Total number of aggregations computed
55    pub aggregations_computed_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
56
57    /// Duration of aggregation computation in seconds
58    pub aggregation_duration_seconds: Family<Vec<(String, String)>, Histogram>,
59
60    /// Total number of aggregation errors
61    pub aggregation_errors_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
62
63    // === State Backend Metrics ===
64    /// Total number of state operations
65    pub state_operations_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
66
67    /// Duration of state operations in seconds
68    pub state_operation_duration_seconds: Family<Vec<(String, String)>, Histogram>,
69
70    /// Total number of state cache hits
71    pub state_cache_hits_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
72
73    /// Total number of state cache misses
74    pub state_cache_misses_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
75
76    /// Current size of state in bytes
77    pub state_size_bytes: Family<Vec<(String, String)>, Gauge<i64, AtomicI64>>,
78
79    /// Total number of state evictions
80    pub state_evictions_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
81
82    // === Deduplication Metrics ===
83    /// Total number of events checked for deduplication
84    pub dedup_events_checked_total: Counter<u64, AtomicU64>,
85
86    /// Total number of duplicate events found
87    pub dedup_duplicates_found_total: Counter<u64, AtomicU64>,
88
89    /// Current size of deduplication cache
90    pub dedup_cache_size: Gauge<i64, AtomicI64>,
91
92    /// Total number of deduplication cache evictions
93    pub dedup_cache_evictions_total: Counter<u64, AtomicU64>,
94
95    // === Normalization Metrics ===
96    /// Total number of events normalized
97    pub normalization_events_total: Counter<u64, AtomicU64>,
98
99    /// Total number of fill operations performed
100    pub normalization_fill_operations_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
101
102    /// Total number of gaps filled in time series
103    pub normalization_gaps_filled_total: Counter<u64, AtomicU64>,
104
105    /// Total number of outliers detected
106    pub normalization_outliers_detected_total: Counter<u64, AtomicU64>,
107
108    // === Watermark Metrics ===
109    /// Current watermark lag in seconds
110    pub watermark_lag_seconds: Gauge<f64, AtomicU64>,
111
112    /// Total number of watermark updates
113    pub watermark_updates_total: Counter<u64, AtomicU64>,
114
115    /// Total number of out-of-order events
116    pub out_of_order_events_total: Counter<u64, AtomicU64>,
117
118    // === Kafka Metrics ===
119    /// Total number of messages consumed from Kafka
120    pub kafka_messages_consumed_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
121
122    /// Total number of messages produced to Kafka
123    pub kafka_messages_produced_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
124
125    /// Kafka consumer lag (messages)
126    pub kafka_consumer_lag: Family<Vec<(String, String)>, Gauge<i64, AtomicI64>>,
127
128    /// Kafka offset commit errors
129    pub kafka_offset_commit_errors_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
130
131    // === Pipeline Metrics ===
132    /// Total number of pipeline errors
133    pub pipeline_errors_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
134
135    /// Number of active pipeline operators
136    pub pipeline_operators_active: Gauge<i64, AtomicI64>,
137
138    /// Duration of operator execution in seconds
139    pub operator_duration_seconds: Family<Vec<(String, String)>, Histogram>,
140}
141
142impl ProcessorMetrics {
143    /// Create a new ProcessorMetrics instance and register all metrics
144    pub fn new(registry: &mut Registry) -> Self {
145        let metrics = Self {
146            // Stream Processing
147            events_received_total: Counter::default(),
148            events_processed_total: Family::default(),
149            event_processing_duration_seconds: Family::new_with_constructor(Self::duration_histogram),
150            pipeline_lag_seconds: Gauge::default(),
151            backpressure_events: Gauge::default(),
152
153            // Windows
154            windows_created_total: Family::default(),
155            windows_triggered_total: Family::default(),
156            windows_active: Family::default(),
157            window_size_events: Family::new_with_constructor(Self::size_histogram),
158            late_events_total: Counter::default(),
159
160            // Aggregations
161            aggregations_computed_total: Family::default(),
162            aggregation_duration_seconds: Family::new_with_constructor(Self::duration_histogram),
163            aggregation_errors_total: Family::default(),
164
165            // State Backend
166            state_operations_total: Family::default(),
167            state_operation_duration_seconds: Family::new_with_constructor(Self::duration_histogram),
168            state_cache_hits_total: Family::default(),
169            state_cache_misses_total: Family::default(),
170            state_size_bytes: Family::default(),
171            state_evictions_total: Family::default(),
172
173            // Deduplication
174            dedup_events_checked_total: Counter::default(),
175            dedup_duplicates_found_total: Counter::default(),
176            dedup_cache_size: Gauge::default(),
177            dedup_cache_evictions_total: Counter::default(),
178
179            // Normalization
180            normalization_events_total: Counter::default(),
181            normalization_fill_operations_total: Family::default(),
182            normalization_gaps_filled_total: Counter::default(),
183            normalization_outliers_detected_total: Counter::default(),
184
185            // Watermarks
186            watermark_lag_seconds: Gauge::default(),
187            watermark_updates_total: Counter::default(),
188            out_of_order_events_total: Counter::default(),
189
190            // Kafka
191            kafka_messages_consumed_total: Family::default(),
192            kafka_messages_produced_total: Family::default(),
193            kafka_consumer_lag: Family::default(),
194            kafka_offset_commit_errors_total: Family::default(),
195
196            // Pipeline
197            pipeline_errors_total: Family::default(),
198            pipeline_operators_active: Gauge::default(),
199            operator_duration_seconds: Family::new_with_constructor(Self::duration_histogram),
200        };
201
202        // Register all metrics with help text and units
203        registry.register(
204            "processor_events_received",
205            "Total number of events received by the processor",
206            metrics.events_received_total.clone(),
207        );
208
209        registry.register(
210            "processor_events_processed",
211            "Total number of events processed by result",
212            metrics.events_processed_total.clone(),
213        );
214
215        registry.register(
216            "processor_event_processing_duration_seconds",
217            "Duration of event processing",
218            metrics.event_processing_duration_seconds.clone(),
219        );
220
221        registry.register(
222            "processor_pipeline_lag_seconds",
223            "Current pipeline lag",
224            metrics.pipeline_lag_seconds.clone(),
225        );
226
227        registry.register(
228            "processor_backpressure_events",
229            "Number of events waiting due to backpressure",
230            metrics.backpressure_events.clone(),
231        );
232
233        registry.register(
234            "processor_windows_created",
235            "Total number of windows created by type",
236            metrics.windows_created_total.clone(),
237        );
238
239        registry.register(
240            "processor_windows_triggered",
241            "Total number of windows triggered by type",
242            metrics.windows_triggered_total.clone(),
243        );
244
245        registry.register(
246            "processor_windows_active",
247            "Number of currently active windows by type",
248            metrics.windows_active.clone(),
249        );
250
251        registry.register(
252            "processor_window_size_events",
253            "Distribution of window sizes in number of events",
254            metrics.window_size_events.clone(),
255        );
256
257        registry.register(
258            "processor_late_events",
259            "Total number of late events arriving after watermark",
260            metrics.late_events_total.clone(),
261        );
262
263        registry.register(
264            "processor_aggregations_computed",
265            "Total number of aggregations computed by type",
266            metrics.aggregations_computed_total.clone(),
267        );
268
269        registry.register(
270            "processor_aggregation_duration_seconds",
271            "Duration of aggregation computation",
272            metrics.aggregation_duration_seconds.clone(),
273        );
274
275        registry.register(
276            "processor_aggregation_errors",
277            "Total number of aggregation errors by type",
278            metrics.aggregation_errors_total.clone(),
279        );
280
281        registry.register(
282            "processor_state_operations",
283            "Total number of state operations by operation and backend",
284            metrics.state_operations_total.clone(),
285        );
286
287        registry.register(
288            "processor_state_operation_duration_seconds",
289            "Duration of state operations",
290            metrics.state_operation_duration_seconds.clone(),
291        );
292
293        registry.register(
294            "processor_state_cache_hits",
295            "Total number of state cache hits by layer",
296            metrics.state_cache_hits_total.clone(),
297        );
298
299        registry.register(
300            "processor_state_cache_misses",
301            "Total number of state cache misses by layer",
302            metrics.state_cache_misses_total.clone(),
303        );
304
305        registry.register(
306            "processor_state_size_bytes",
307            "Current size of state in bytes by backend",
308            metrics.state_size_bytes.clone(),
309        );
310
311        registry.register(
312            "processor_state_evictions",
313            "Total number of state evictions by backend",
314            metrics.state_evictions_total.clone(),
315        );
316
317        registry.register(
318            "processor_dedup_events_checked",
319            "Total number of events checked for deduplication",
320            metrics.dedup_events_checked_total.clone(),
321        );
322
323        registry.register(
324            "processor_dedup_duplicates_found",
325            "Total number of duplicate events found",
326            metrics.dedup_duplicates_found_total.clone(),
327        );
328
329        registry.register(
330            "processor_dedup_cache_size",
331            "Current size of deduplication cache",
332            metrics.dedup_cache_size.clone(),
333        );
334
335        registry.register(
336            "processor_dedup_cache_evictions",
337            "Total number of deduplication cache evictions",
338            metrics.dedup_cache_evictions_total.clone(),
339        );
340
341        registry.register(
342            "processor_normalization_events",
343            "Total number of events normalized",
344            metrics.normalization_events_total.clone(),
345        );
346
347        registry.register(
348            "processor_normalization_fill_operations",
349            "Total number of fill operations by strategy",
350            metrics.normalization_fill_operations_total.clone(),
351        );
352
353        registry.register(
354            "processor_normalization_gaps_filled",
355            "Total number of gaps filled in time series",
356            metrics.normalization_gaps_filled_total.clone(),
357        );
358
359        registry.register(
360            "processor_normalization_outliers_detected",
361            "Total number of outliers detected",
362            metrics.normalization_outliers_detected_total.clone(),
363        );
364
365        registry.register(
366            "processor_watermark_lag_seconds",
367            "Current watermark lag",
368            metrics.watermark_lag_seconds.clone(),
369        );
370
371        registry.register(
372            "processor_watermark_updates",
373            "Total number of watermark updates",
374            metrics.watermark_updates_total.clone(),
375        );
376
377        registry.register(
378            "processor_out_of_order_events",
379            "Total number of out-of-order events",
380            metrics.out_of_order_events_total.clone(),
381        );
382
383        registry.register(
384            "processor_kafka_messages_consumed",
385            "Total number of messages consumed from Kafka",
386            metrics.kafka_messages_consumed_total.clone(),
387        );
388
389        registry.register(
390            "processor_kafka_messages_produced",
391            "Total number of messages produced to Kafka",
392            metrics.kafka_messages_produced_total.clone(),
393        );
394
395        registry.register(
396            "processor_kafka_consumer_lag",
397            "Kafka consumer lag in messages",
398            metrics.kafka_consumer_lag.clone(),
399        );
400
401        registry.register(
402            "processor_kafka_offset_commit_errors",
403            "Kafka offset commit errors",
404            metrics.kafka_offset_commit_errors_total.clone(),
405        );
406
407        registry.register(
408            "processor_pipeline_errors",
409            "Total number of pipeline errors by type",
410            metrics.pipeline_errors_total.clone(),
411        );
412
413        registry.register(
414            "processor_pipeline_operators_active",
415            "Number of active pipeline operators",
416            metrics.pipeline_operators_active.clone(),
417        );
418
419        registry.register(
420            "processor_operator_duration_seconds",
421            "Duration of operator execution",
422            metrics.operator_duration_seconds.clone(),
423        );
424
425        metrics
426    }
427
428    /// Create a new instance registered with the global registry
429    pub fn global() -> Arc<Self> {
430        let registry = MetricsRegistry::global();
431        let binding = registry.registry();
432        let mut reg = binding.write();
433        Arc::new(Self::new(&mut reg))
434    }
435
436    // === Convenience methods for common metric operations ===
437
438    /// Record an event being received
439    #[inline]
440    pub fn record_event_received(&self) {
441        self.events_received_total.inc();
442    }
443
444    /// Record an event being processed
445    #[inline]
446    pub fn record_event_processed(&self, result: ResultLabel) {
447        self.events_processed_total
448            .get_or_create(&vec![(LabelNames::RESULT.to_string(), result.to_string())])
449            .inc();
450    }
451
452    /// Record event processing duration
453    #[inline]
454    pub fn record_event_processing_duration(&self, duration_secs: f64, result: ResultLabel) {
455        self.event_processing_duration_seconds
456            .get_or_create(&vec![(LabelNames::RESULT.to_string(), result.to_string())])
457            .observe(duration_secs);
458    }
459
460    /// Update pipeline lag
461    #[inline]
462    pub fn set_pipeline_lag(&self, lag_secs: f64) {
463        self.pipeline_lag_seconds.set(lag_secs);
464    }
465
466    /// Update backpressure events count
467    #[inline]
468    pub fn set_backpressure_events(&self, count: i64) {
469        self.backpressure_events.set(count);
470    }
471
472    /// Record window creation
473    #[inline]
474    pub fn record_window_created(&self, window_type: WindowTypeLabel) {
475        self.windows_created_total
476            .get_or_create(&vec![(
477                LabelNames::WINDOW_TYPE.to_string(),
478                window_type.to_string(),
479            )])
480            .inc();
481    }
482
483    /// Record window trigger
484    #[inline]
485    pub fn record_window_triggered(&self, window_type: WindowTypeLabel) {
486        self.windows_triggered_total
487            .get_or_create(&vec![(
488                LabelNames::WINDOW_TYPE.to_string(),
489                window_type.to_string(),
490            )])
491            .inc();
492    }
493
494    /// Update active windows count
495    #[inline]
496    pub fn set_windows_active(&self, window_type: WindowTypeLabel, count: i64) {
497        self.windows_active
498            .get_or_create(&vec![(
499                LabelNames::WINDOW_TYPE.to_string(),
500                window_type.to_string(),
501            )])
502            .set(count);
503    }
504
505    /// Record window size
506    #[inline]
507    pub fn record_window_size(&self, window_type: WindowTypeLabel, size: f64) {
508        self.window_size_events
509            .get_or_create(&vec![(
510                LabelNames::WINDOW_TYPE.to_string(),
511                window_type.to_string(),
512            )])
513            .observe(size);
514    }
515
516    /// Record late event
517    #[inline]
518    pub fn record_late_event(&self) {
519        self.late_events_total.inc();
520    }
521
522    /// Record state operation
523    #[inline]
524    pub fn record_state_operation(
525        &self,
526        operation: OperationLabel,
527        backend: BackendLabel,
528        duration_secs: f64,
529    ) {
530        let labels = vec![
531            (LabelNames::OPERATION.to_string(), operation.to_string()),
532            (LabelNames::BACKEND.to_string(), backend.to_string()),
533        ];
534
535        self.state_operations_total.get_or_create(&labels).inc();
536        self.state_operation_duration_seconds
537            .get_or_create(&labels)
538            .observe(duration_secs);
539    }
540
541    /// Record state cache hit
542    #[inline]
543    pub fn record_state_cache_hit(&self, layer: CacheLayerLabel) {
544        self.state_cache_hits_total
545            .get_or_create(&vec![(LabelNames::LAYER.to_string(), layer.to_string())])
546            .inc();
547    }
548
549    /// Record state cache miss
550    #[inline]
551    pub fn record_state_cache_miss(&self, layer: CacheLayerLabel) {
552        self.state_cache_misses_total
553            .get_or_create(&vec![(LabelNames::LAYER.to_string(), layer.to_string())])
554            .inc();
555    }
556
557    /// Update state size
558    #[inline]
559    pub fn set_state_size_bytes(&self, backend: BackendLabel, size: i64) {
560        self.state_size_bytes
561            .get_or_create(&vec![(LabelNames::BACKEND.to_string(), backend.to_string())])
562            .set(size);
563    }
564
565    /// Record deduplication check
566    #[inline]
567    pub fn record_dedup_check(&self, is_duplicate: bool) {
568        self.dedup_events_checked_total.inc();
569        if is_duplicate {
570            self.dedup_duplicates_found_total.inc();
571        }
572    }
573
574    /// Update deduplication cache size
575    #[inline]
576    pub fn set_dedup_cache_size(&self, size: i64) {
577        self.dedup_cache_size.set(size);
578    }
579
580    /// Record normalization fill operation
581    #[inline]
582    pub fn record_normalization_fill(&self, strategy: StrategyLabel) {
583        self.normalization_fill_operations_total
584            .get_or_create(&vec![(LabelNames::STRATEGY.to_string(), strategy.to_string())])
585            .inc();
586    }
587
588    /// Update watermark lag
589    #[inline]
590    pub fn set_watermark_lag(&self, lag_secs: f64) {
591        self.watermark_lag_seconds.set(lag_secs);
592    }
593
594    /// Record watermark update
595    #[inline]
596    pub fn record_watermark_update(&self) {
597        self.watermark_updates_total.inc();
598    }
599
600    /// Record out-of-order event
601    #[inline]
602    pub fn record_out_of_order_event(&self) {
603        self.out_of_order_events_total.inc();
604    }
605
606    /// Helper function to create duration histograms (1ms to 30s)
607    fn duration_histogram() -> Histogram {
608        Histogram::new(exponential_buckets(0.001, 2.0, 16))
609    }
610
611    /// Helper function to create size histograms (1 to 10000 events)
612    fn size_histogram() -> Histogram {
613        Histogram::new(exponential_buckets(1.0, 2.0, 14))
614    }
615}
616
617/// Snapshot of metrics for testing and debugging
618#[derive(Debug, Default)]
619pub struct MetricsSnapshot {
620    pub events_received: u64,
621    pub events_processed_success: u64,
622    pub events_processed_error: u64,
623    pub windows_created: u64,
624    pub duplicates_found: u64,
625}
626
627impl MetricsSnapshot {
628    /// Create a snapshot from the current metrics
629    pub fn capture(metrics: &ProcessorMetrics) -> Self {
630        Self {
631            events_received: metrics.events_received_total.get(),
632            events_processed_success: metrics
633                .events_processed_total
634                .get_or_create(&vec![(
635                    LabelNames::RESULT.to_string(),
636                    ResultLabel::Success.to_string(),
637                )])
638                .get(),
639            events_processed_error: metrics
640                .events_processed_total
641                .get_or_create(&vec![(
642                    LabelNames::RESULT.to_string(),
643                    ResultLabel::Error.to_string(),
644                )])
645                .get(),
646            windows_created: metrics
647                .windows_created_total
648                .get_or_create(&vec![(
649                    LabelNames::WINDOW_TYPE.to_string(),
650                    WindowTypeLabel::Tumbling.to_string(),
651                )])
652                .get(),
653            duplicates_found: metrics.dedup_duplicates_found_total.get(),
654        }
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661
662    #[test]
663    fn test_metrics_creation() {
664        let mut registry = Registry::default();
665        let metrics = ProcessorMetrics::new(&mut registry);
666
667        // Test basic operations
668        metrics.record_event_received();
669        assert_eq!(metrics.events_received_total.get(), 1);
670
671        metrics.record_event_processed(ResultLabel::Success);
672        assert_eq!(
673            metrics
674                .events_processed_total
675                .get_or_create(&vec![(
676                    LabelNames::RESULT.to_string(),
677                    ResultLabel::Success.to_string()
678                )])
679                .get(),
680            1
681        );
682    }
683
684    #[test]
685    fn test_window_metrics() {
686        let mut registry = Registry::default();
687        let metrics = ProcessorMetrics::new(&mut registry);
688
689        metrics.record_window_created(WindowTypeLabel::Tumbling);
690        metrics.record_window_triggered(WindowTypeLabel::Tumbling);
691        metrics.set_windows_active(WindowTypeLabel::Tumbling, 5);
692
693        assert_eq!(
694            metrics
695                .windows_active
696                .get_or_create(&vec![(
697                    LabelNames::WINDOW_TYPE.to_string(),
698                    WindowTypeLabel::Tumbling.to_string()
699                )])
700                .get(),
701            5
702        );
703    }
704
705    #[test]
706    fn test_deduplication_metrics() {
707        let mut registry = Registry::default();
708        let metrics = ProcessorMetrics::new(&mut registry);
709
710        metrics.record_dedup_check(false);
711        metrics.record_dedup_check(true);
712        metrics.record_dedup_check(true);
713
714        assert_eq!(metrics.dedup_events_checked_total.get(), 3);
715        assert_eq!(metrics.dedup_duplicates_found_total.get(), 2);
716    }
717
718    #[test]
719    fn test_state_metrics() {
720        let mut registry = Registry::default();
721        let metrics = ProcessorMetrics::new(&mut registry);
722
723        metrics.record_state_operation(OperationLabel::Get, BackendLabel::Redis, 0.001);
724        metrics.record_state_cache_hit(CacheLayerLabel::L1);
725        metrics.record_state_cache_miss(CacheLayerLabel::L2);
726
727        assert_eq!(
728            metrics
729                .state_cache_hits_total
730                .get_or_create(&vec![(
731                    LabelNames::LAYER.to_string(),
732                    CacheLayerLabel::L1.to_string()
733                )])
734                .get(),
735            1
736        );
737    }
738
739    #[test]
740    fn test_metrics_snapshot() {
741        let mut registry = Registry::default();
742        let metrics = ProcessorMetrics::new(&mut registry);
743
744        metrics.record_event_received();
745        metrics.record_event_processed(ResultLabel::Success);
746        metrics.record_dedup_check(true);
747
748        let snapshot = MetricsSnapshot::capture(&metrics);
749        assert_eq!(snapshot.events_received, 1);
750        assert_eq!(snapshot.events_processed_success, 1);
751        assert_eq!(snapshot.duplicates_found, 1);
752    }
753}