1use 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
19pub struct ProcessorMetrics {
21 pub events_received_total: Counter<u64, AtomicU64>,
24
25 pub events_processed_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
27
28 pub event_processing_duration_seconds: Family<Vec<(String, String)>, Histogram>,
30
31 pub pipeline_lag_seconds: Gauge<f64, AtomicU64>,
33
34 pub backpressure_events: Gauge<i64, AtomicI64>,
36
37 pub windows_created_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
40
41 pub windows_triggered_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
43
44 pub windows_active: Family<Vec<(String, String)>, Gauge<i64, AtomicI64>>,
46
47 pub window_size_events: Family<Vec<(String, String)>, Histogram>,
49
50 pub late_events_total: Counter<u64, AtomicU64>,
52
53 pub aggregations_computed_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
56
57 pub aggregation_duration_seconds: Family<Vec<(String, String)>, Histogram>,
59
60 pub aggregation_errors_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
62
63 pub state_operations_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
66
67 pub state_operation_duration_seconds: Family<Vec<(String, String)>, Histogram>,
69
70 pub state_cache_hits_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
72
73 pub state_cache_misses_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
75
76 pub state_size_bytes: Family<Vec<(String, String)>, Gauge<i64, AtomicI64>>,
78
79 pub state_evictions_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
81
82 pub dedup_events_checked_total: Counter<u64, AtomicU64>,
85
86 pub dedup_duplicates_found_total: Counter<u64, AtomicU64>,
88
89 pub dedup_cache_size: Gauge<i64, AtomicI64>,
91
92 pub dedup_cache_evictions_total: Counter<u64, AtomicU64>,
94
95 pub normalization_events_total: Counter<u64, AtomicU64>,
98
99 pub normalization_fill_operations_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
101
102 pub normalization_gaps_filled_total: Counter<u64, AtomicU64>,
104
105 pub normalization_outliers_detected_total: Counter<u64, AtomicU64>,
107
108 pub watermark_lag_seconds: Gauge<f64, AtomicU64>,
111
112 pub watermark_updates_total: Counter<u64, AtomicU64>,
114
115 pub out_of_order_events_total: Counter<u64, AtomicU64>,
117
118 pub kafka_messages_consumed_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
121
122 pub kafka_messages_produced_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
124
125 pub kafka_consumer_lag: Family<Vec<(String, String)>, Gauge<i64, AtomicI64>>,
127
128 pub kafka_offset_commit_errors_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
130
131 pub pipeline_errors_total: Family<Vec<(String, String)>, Counter<u64, AtomicU64>>,
134
135 pub pipeline_operators_active: Gauge<i64, AtomicI64>,
137
138 pub operator_duration_seconds: Family<Vec<(String, String)>, Histogram>,
140}
141
142impl ProcessorMetrics {
143 pub fn new(registry: &mut Registry) -> Self {
145 let metrics = Self {
146 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_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_computed_total: Family::default(),
162 aggregation_duration_seconds: Family::new_with_constructor(Self::duration_histogram),
163 aggregation_errors_total: Family::default(),
164
165 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 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_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 watermark_lag_seconds: Gauge::default(),
187 watermark_updates_total: Counter::default(),
188 out_of_order_events_total: Counter::default(),
189
190 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_errors_total: Family::default(),
198 pipeline_operators_active: Gauge::default(),
199 operator_duration_seconds: Family::new_with_constructor(Self::duration_histogram),
200 };
201
202 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 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 #[inline]
440 pub fn record_event_received(&self) {
441 self.events_received_total.inc();
442 }
443
444 #[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 #[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 #[inline]
462 pub fn set_pipeline_lag(&self, lag_secs: f64) {
463 self.pipeline_lag_seconds.set(lag_secs);
464 }
465
466 #[inline]
468 pub fn set_backpressure_events(&self, count: i64) {
469 self.backpressure_events.set(count);
470 }
471
472 #[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 #[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 #[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 #[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 #[inline]
518 pub fn record_late_event(&self) {
519 self.late_events_total.inc();
520 }
521
522 #[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 #[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 #[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 #[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 #[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 #[inline]
576 pub fn set_dedup_cache_size(&self, size: i64) {
577 self.dedup_cache_size.set(size);
578 }
579
580 #[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 #[inline]
590 pub fn set_watermark_lag(&self, lag_secs: f64) {
591 self.watermark_lag_seconds.set(lag_secs);
592 }
593
594 #[inline]
596 pub fn record_watermark_update(&self) {
597 self.watermark_updates_total.inc();
598 }
599
600 #[inline]
602 pub fn record_out_of_order_event(&self) {
603 self.out_of_order_events_total.inc();
604 }
605
606 fn duration_histogram() -> Histogram {
608 Histogram::new(exponential_buckets(0.001, 2.0, 16))
609 }
610
611 fn size_histogram() -> Histogram {
613 Histogram::new(exponential_buckets(1.0, 2.0, 14))
614 }
615}
616
617#[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 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 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}