1use crate::aggregation::{Aggregator, CompositeAggregator, AggregationResult};
82use crate::core::{EventTimeExtractor, KeyExtractor, ProcessorEvent};
83use crate::error::{ProcessorError, WindowError};
84use crate::watermark::{BoundedOutOfOrdernessWatermark, WatermarkGenerator};
85use crate::window::{
86 Window, WindowAssigner, WindowManager, WindowTrigger, WindowMetadata,
87 TumblingWindowAssigner, SlidingWindowAssigner, SessionWindowAssigner,
88 OnWatermarkTrigger, ProcessingTimeTrigger, CountTrigger,
89};
90use chrono::{DateTime, Duration, Utc};
91use dashmap::DashMap;
92use futures::Stream;
93use serde::{Deserialize, Serialize};
94use std::collections::HashMap;
95use std::marker::PhantomData;
96use std::pin::Pin;
97use std::sync::Arc;
98use tokio::sync::{mpsc, RwLock};
99use tokio_stream::wrappers::ReceiverStream;
100use tracing::{debug, error, info, trace, warn};
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct WindowResult {
105 pub window: Window,
107 pub key: String,
109 pub results: AggregationResult,
111 pub event_count: u64,
113 pub processed_at: DateTime<Utc>,
115 pub watermark: Option<DateTime<Utc>>,
117}
118
119#[derive(Debug, Clone)]
121pub struct StreamProcessorConfig {
122 pub allow_late_events: bool,
124 pub late_event_threshold: Duration,
126 pub watermark_interval: Duration,
128 pub window_retention: Duration,
130 pub max_windows_per_key: usize,
132 pub result_buffer_size: usize,
134}
135
136impl Default for StreamProcessorConfig {
137 fn default() -> Self {
138 Self {
139 allow_late_events: true,
140 late_event_threshold: Duration::minutes(5),
141 watermark_interval: Duration::seconds(1),
142 window_retention: Duration::hours(1),
143 max_windows_per_key: 1000,
144 result_buffer_size: 1000,
145 }
146 }
147}
148
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151pub struct ProcessorStats {
152 pub events_processed: u64,
154 pub events_dropped: u64,
156 pub windows_created: u64,
158 pub windows_fired: u64,
160 pub active_windows: usize,
162 pub current_watermark: Option<DateTime<Utc>>,
164 pub latency_p50_ms: f64,
166 pub latency_p95_ms: f64,
167 pub latency_p99_ms: f64,
168 pub last_update: DateTime<Utc>,
170}
171
172struct KeyState<A: Aggregator> {
174 aggregators: HashMap<String, A>,
176 last_event_time: Option<DateTime<Utc>>,
178}
179
180impl<A: Aggregator> KeyState<A> {
181 fn new() -> Self {
182 Self {
183 aggregators: HashMap::new(),
184 last_event_time: None,
185 }
186 }
187}
188
189pub struct StreamProcessor<T, A, W, Tr>
191where
192 A: Aggregator<Input = f64, Output = f64> + Clone + Send + Sync + 'static,
193 W: WindowAssigner + Clone + Send + Sync + 'static,
194 Tr: WindowTrigger + Clone + Send + Sync + 'static,
195{
196 config: StreamProcessorConfig,
198 window_manager: Arc<RwLock<WindowManager<W, Tr>>>,
200 watermark_generator: Arc<RwLock<dyn WatermarkGenerator>>,
202 aggregators: Arc<DashMap<String, KeyState<A>>>,
204 result_tx: mpsc::Sender<WindowResult>,
206 result_rx: Option<mpsc::Receiver<WindowResult>>,
208 stats: Arc<RwLock<ProcessorStats>>,
210 aggregator_factory: Arc<dyn Fn(&str) -> A + Send + Sync>,
212 _phantom: PhantomData<T>,
214}
215
216impl<T, A, W, Tr> StreamProcessor<T, A, W, Tr>
217where
218 A: Aggregator<Input = f64, Output = f64> + Clone + Send + Sync + 'static,
219 W: WindowAssigner + Clone + Send + Sync + 'static,
220 Tr: WindowTrigger + Clone + Send + Sync + 'static,
221{
222 pub fn new<F>(
224 config: StreamProcessorConfig,
225 assigner: W,
226 trigger: Tr,
227 aggregator_factory: F,
228 ) -> Self
229 where
230 F: Fn(&str) -> A + Send + Sync + 'static,
231 {
232 let window_manager = WindowManager::with_late_events(
233 assigner,
234 trigger,
235 config.allow_late_events,
236 config.late_event_threshold,
237 );
238
239 let (result_tx, result_rx) = mpsc::channel(config.result_buffer_size);
240
241 let watermark_threshold = config.late_event_threshold.to_std().unwrap_or(std::time::Duration::from_secs(300));
243
244 Self {
245 config,
246 window_manager: Arc::new(RwLock::new(window_manager)),
247 watermark_generator: Arc::new(RwLock::new(
248 BoundedOutOfOrdernessWatermark::new(
249 watermark_threshold,
250 None,
251 )
252 )),
253 aggregators: Arc::new(DashMap::new()),
254 result_tx,
255 result_rx: Some(result_rx),
256 stats: Arc::new(RwLock::new(ProcessorStats::default())),
257 aggregator_factory: Arc::new(aggregator_factory),
258 _phantom: PhantomData,
259 }
260 }
261
262 pub async fn process_event(
271 &self,
272 key: String,
273 event_time: DateTime<Utc>,
274 value: f64,
275 ) -> Result<(), ProcessorError> {
276 let start_time = Utc::now();
277
278 {
280 let mut stats = self.stats.write().await;
281 stats.events_processed += 1;
282 stats.last_update = Utc::now();
283 }
284
285 let windows = {
287 let mut manager = self.window_manager.write().await;
288 match manager.assign_event(event_time) {
289 Ok(windows) => windows,
290 Err(e) => {
291 warn!(
292 error = %e,
293 event_time = %event_time,
294 "Failed to assign event to windows"
295 );
296 let mut stats = self.stats.write().await;
297 stats.events_dropped += 1;
298 return Err(e);
299 }
300 }
301 };
302
303 if windows.is_empty() {
304 let mut stats = self.stats.write().await;
306 stats.events_dropped += 1;
307 return Ok(());
308 }
309
310 trace!(
311 key = %key,
312 event_time = %event_time,
313 window_count = windows.len(),
314 "Processing event"
315 );
316
317 let mut key_state = self.aggregators
319 .entry(key.clone())
320 .or_insert_with(KeyState::new);
321
322 for window in &windows {
323 let aggregator = key_state.aggregators
324 .entry(window.id.clone())
325 .or_insert_with(|| (self.aggregator_factory)(&key));
326
327 if let Err(e) = aggregator.update(value) {
328 error!(
329 window_id = %window.id,
330 error = %e,
331 "Failed to update aggregator"
332 );
333 return Err(ProcessorError::Aggregation(
334 crate::error::AggregationError::CorruptedState {
335 aggregation_type: "unknown".to_string(),
336 details: e.to_string(),
337 }
338 ));
339 }
340 }
341
342 key_state.last_event_time = Some(event_time);
343 drop(key_state);
344
345 let watermark = {
347 let mut wm_gen = self.watermark_generator.write().await;
348 wm_gen.on_event(event_time.timestamp_millis(), 0);
349 wm_gen.current_watermark()
350 };
351
352 let ready_windows = {
354 let mut manager = self.window_manager.write().await;
355 manager.advance_watermark(watermark.to_datetime())?
356 };
357
358 for window_id in ready_windows {
360 self.fire_window(&key, &window_id, watermark.to_datetime()).await?;
361 }
362
363 let latency = (Utc::now() - start_time).num_milliseconds() as f64;
365 self.update_latency_stats(latency).await;
366
367 Ok(())
368 }
369
370 async fn fire_window(
372 &self,
373 key: &str,
374 window_id: &str,
375 watermark: DateTime<Utc>,
376 ) -> Result<(), ProcessorError> {
377 let metadata = {
379 let manager = self.window_manager.read().await;
380 manager.get_window(window_id)
381 };
382
383 let metadata = match metadata {
384 Some(m) => m,
385 None => {
386 warn!(window_id = %window_id, "Window not found");
387 return Ok(());
388 }
389 };
390
391 let results = {
393 let mut key_state = match self.aggregators.get_mut(key) {
394 Some(state) => state,
395 None => {
396 warn!(key = %key, window_id = %window_id, "Key state not found");
397 return Ok(());
398 }
399 };
400
401 let aggregator = match key_state.aggregators.remove(window_id) {
402 Some(agg) => agg,
403 None => {
404 warn!(key = %key, window_id = %window_id, "Aggregator not found");
405 return Ok(());
406 }
407 };
408
409 if let Some(composite) = (&aggregator as &dyn std::any::Any).downcast_ref::<CompositeAggregator>() {
413 composite.finalize().map_err(|e| {
414 ProcessorError::Aggregation(crate::error::AggregationError::CorruptedState {
415 aggregation_type: "composite".to_string(),
416 details: e.to_string(),
417 })
418 })?
419 } else {
420 let value = aggregator.finalize().map_err(|e| {
422 ProcessorError::Aggregation(crate::error::AggregationError::CorruptedState {
423 aggregation_type: "simple".to_string(),
424 details: e.to_string(),
425 })
426 })?;
427
428 let mut result = AggregationResult::new();
429 result.set_value("value", value);
430 result.set_count("count", aggregator.count());
431 result
432 }
433 };
434
435 let window_result = WindowResult {
437 window: metadata.window.clone(),
438 key: key.to_string(),
439 results,
440 event_count: metadata.stats.event_count,
441 processed_at: Utc::now(),
442 watermark: Some(watermark),
443 };
444
445 debug!(
446 window_id = %window_id,
447 key = %key,
448 event_count = metadata.stats.event_count,
449 "Window fired"
450 );
451
452 if let Err(e) = self.result_tx.send(window_result).await {
454 error!(error = %e, "Failed to send window result");
455 }
456
457 {
459 let mut stats = self.stats.write().await;
460 stats.windows_fired += 1;
461 stats.current_watermark = Some(watermark);
462 }
463
464 Ok(())
465 }
466
467 async fn update_latency_stats(&self, latency_ms: f64) {
469 let mut stats = self.stats.write().await;
472
473 if stats.latency_p50_ms == 0.0 {
474 stats.latency_p50_ms = latency_ms;
475 stats.latency_p95_ms = latency_ms;
476 stats.latency_p99_ms = latency_ms;
477 } else {
478 stats.latency_p50_ms = 0.9 * stats.latency_p50_ms + 0.1 * latency_ms;
480 stats.latency_p95_ms = 0.95 * stats.latency_p95_ms + 0.05 * latency_ms.max(stats.latency_p95_ms);
481 stats.latency_p99_ms = 0.99 * stats.latency_p99_ms + 0.01 * latency_ms.max(stats.latency_p99_ms);
482 }
483 }
484
485 pub fn results(&mut self) -> Option<impl Stream<Item = WindowResult>> {
487 self.result_rx.take().map(ReceiverStream::new)
488 }
489
490 pub async fn stats(&self) -> ProcessorStats {
492 let mut stats = self.stats.read().await.clone();
493
494 let manager = self.window_manager.read().await;
496 stats.active_windows = manager.active_window_count();
497 stats.current_watermark = manager.current_watermark();
498
499 stats
500 }
501
502 pub async fn cleanup(&self) -> Result<usize, ProcessorError> {
504 let mut manager = self.window_manager.write().await;
505 let removed = manager.cleanup_old_windows(self.config.window_retention);
506
507 if removed > 0 {
508 info!(removed, "Cleaned up old windows");
509 }
510
511 Ok(removed)
512 }
513
514 pub async fn reset(&self) {
516 let mut manager = self.window_manager.write().await;
517 manager.reset();
518
519 self.aggregators.clear();
524
525 let mut stats = self.stats.write().await;
526 *stats = ProcessorStats::default();
527
528 info!("Stream processor reset");
529 }
530}
531
532pub struct StreamProcessorBuilder<T, A> {
534 config: StreamProcessorConfig,
535 _phantom: PhantomData<(T, A)>,
536}
537
538impl<T, A> Default for StreamProcessorBuilder<T, A>
539where
540 A: Aggregator<Input = f64, Output = f64> + Clone + Send + Sync + 'static,
541{
542 fn default() -> Self {
543 Self::new()
544 }
545}
546
547impl<T, A> StreamProcessorBuilder<T, A>
548where
549 A: Aggregator<Input = f64, Output = f64> + Clone + Send + Sync + 'static,
550{
551 pub fn new() -> Self {
553 Self {
554 config: StreamProcessorConfig::default(),
555 _phantom: PhantomData,
556 }
557 }
558
559 pub fn with_config(mut self, config: StreamProcessorConfig) -> Self {
561 self.config = config;
562 self
563 }
564
565 pub fn allow_late_events(mut self, allow: bool, threshold: Duration) -> Self {
567 self.config.allow_late_events = allow;
568 self.config.late_event_threshold = threshold;
569 self
570 }
571
572 pub fn watermark_interval(mut self, interval: Duration) -> Self {
574 self.config.watermark_interval = interval;
575 self
576 }
577
578 pub fn build_tumbling<F>(
580 self,
581 window_size: Duration,
582 aggregator_factory: F,
583 ) -> StreamProcessor<T, A, TumblingWindowAssigner, OnWatermarkTrigger>
584 where
585 F: Fn(&str) -> A + Send + Sync + 'static,
586 {
587 let assigner = TumblingWindowAssigner::new(window_size);
588 let trigger = OnWatermarkTrigger::new();
589
590 StreamProcessor::new(self.config, assigner, trigger, aggregator_factory)
591 }
592
593 pub fn build_sliding<F>(
595 self,
596 window_size: Duration,
597 slide: Duration,
598 aggregator_factory: F,
599 ) -> StreamProcessor<T, A, SlidingWindowAssigner, OnWatermarkTrigger>
600 where
601 F: Fn(&str) -> A + Send + Sync + 'static,
602 {
603 let assigner = SlidingWindowAssigner::new(window_size, slide);
604 let trigger = OnWatermarkTrigger::new();
605
606 StreamProcessor::new(self.config, assigner, trigger, aggregator_factory)
607 }
608
609 pub fn build_session<F>(
611 self,
612 gap: Duration,
613 aggregator_factory: F,
614 ) -> StreamProcessor<T, A, SessionWindowAssigner, OnWatermarkTrigger>
615 where
616 F: Fn(&str) -> A + Send + Sync + 'static,
617 {
618 let assigner = SessionWindowAssigner::new(gap);
619 let trigger = OnWatermarkTrigger::new();
620
621 StreamProcessor::new(self.config, assigner, trigger, aggregator_factory)
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::aggregation::AverageAggregator;
629 use tokio_stream::StreamExt;
630
631 #[tokio::test]
632 async fn test_stream_processor_basic() {
633 let processor = StreamProcessorBuilder::new()
634 .build_tumbling(
635 Duration::seconds(1),
636 |_key| AverageAggregator::new(),
637 );
638
639 let base_time = Utc::now();
641 processor.process_event("key1".to_string(), base_time, 10.0).await.unwrap();
642 processor.process_event("key1".to_string(), base_time + Duration::milliseconds(500), 20.0).await.unwrap();
643
644 let stats = processor.stats().await;
645 assert_eq!(stats.events_processed, 2);
646 }
647
648 #[tokio::test]
649 async fn test_stream_processor_windowing() {
650 let mut processor = StreamProcessorBuilder::new()
651 .build_tumbling(
652 Duration::milliseconds(1000),
653 |_key| AverageAggregator::new(),
654 );
655
656 let mut results_stream = processor.results().unwrap();
657
658 let base_time = Utc::now();
660 processor.process_event("key1".to_string(), base_time, 10.0).await.unwrap();
661 processor.process_event("key1".to_string(), base_time + Duration::milliseconds(500), 20.0).await.unwrap();
662
663 processor.process_event(
665 "key1".to_string(),
666 base_time + Duration::milliseconds(2000),
667 30.0
668 ).await.unwrap();
669
670 tokio::time::timeout(
672 std::time::Duration::from_secs(1),
673 results_stream.next()
674 ).await.ok();
675 }
676
677 #[tokio::test]
678 async fn test_stream_processor_multiple_keys() {
679 let processor = StreamProcessorBuilder::new()
680 .build_tumbling(
681 Duration::seconds(1),
682 |_key| AverageAggregator::new(),
683 );
684
685 let base_time = Utc::now();
686 processor.process_event("key1".to_string(), base_time, 10.0).await.unwrap();
687 processor.process_event("key2".to_string(), base_time, 20.0).await.unwrap();
688 processor.process_event("key1".to_string(), base_time + Duration::milliseconds(500), 30.0).await.unwrap();
689
690 let stats = processor.stats().await;
691 assert_eq!(stats.events_processed, 3);
692 }
693
694 #[tokio::test]
695 async fn test_stream_processor_cleanup() {
696 let processor = StreamProcessorBuilder::new()
697 .build_tumbling(
698 Duration::milliseconds(100),
699 |_key| AverageAggregator::new(),
700 );
701
702 let base_time = Utc::now();
703 processor.process_event("key1".to_string(), base_time, 10.0).await.unwrap();
704
705 processor.process_event(
707 "key1".to_string(),
708 base_time + Duration::seconds(1),
709 20.0
710 ).await.unwrap();
711
712 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
714 let removed = processor.cleanup().await.unwrap();
715 assert!(removed > 0);
716 }
717}