1use crate::core::{EventTimeExtractor, KeyExtractor, ProcessorEvent};
7use crate::error::{ProcessorError, Result};
8use crate::pipeline::builder::StreamPipeline;
9use crate::watermark::{BoundedOutOfOrdernessWatermark, Watermark, WatermarkGenerator};
10use crate::window::{
11 SlidingWindowAssigner, SessionWindowAssigner,
12 TumblingWindowAssigner, Window, WindowAssigner,
13};
14use dashmap::DashMap;
15use serde::{Deserialize, Serialize};
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::Arc;
18use std::time::Duration;
19use tokio::sync::{mpsc, RwLock};
20use tokio::time;
21use tracing::{debug, error, info, trace, warn};
22
23#[derive(Debug, Clone, Serialize, Deserialize, Default)]
25pub struct ExecutorStats {
26 pub events_processed: u64,
28
29 pub events_dropped: u64,
31
32 pub windows_created: u64,
34
35 pub windows_triggered: u64,
37
38 pub aggregations_computed: u64,
40
41 pub current_watermark: i64,
43
44 pub active_windows: u64,
46
47 pub bytes_processed: u64,
49
50 pub errors: u64,
52}
53
54impl ExecutorStats {
55 pub fn new() -> Self {
57 Self::default()
58 }
59
60 pub fn inc_events_processed(&mut self) {
62 self.events_processed += 1;
63 }
64
65 pub fn inc_events_dropped(&mut self) {
67 self.events_dropped += 1;
68 }
69
70 pub fn inc_windows_created(&mut self) {
72 self.windows_created += 1;
73 }
74
75 pub fn inc_windows_triggered(&mut self) {
77 self.windows_triggered += 1;
78 }
79
80 pub fn inc_aggregations_computed(&mut self) {
82 self.aggregations_computed += 1;
83 }
84
85 pub fn update_watermark(&mut self, watermark: i64) {
87 self.current_watermark = watermark;
88 }
89
90 pub fn set_active_windows(&mut self, count: u64) {
92 self.active_windows = count;
93 }
94
95 pub fn add_bytes_processed(&mut self, bytes: u64) {
97 self.bytes_processed += bytes;
98 }
99
100 pub fn inc_errors(&mut self) {
102 self.errors += 1;
103 }
104
105 pub fn events_per_second(&self, elapsed_seconds: f64) -> f64 {
107 if elapsed_seconds > 0.0 {
108 self.events_processed as f64 / elapsed_seconds
109 } else {
110 0.0
111 }
112 }
113
114 pub fn throughput_mbps(&self, elapsed_seconds: f64) -> f64 {
116 if elapsed_seconds > 0.0 {
117 (self.bytes_processed as f64 / 1_048_576.0) / elapsed_seconds
118 } else {
119 0.0
120 }
121 }
122}
123
124#[derive(Debug, Clone)]
126struct WindowState<T> {
127 window: Window,
129
130 events: Vec<T>,
132
133 last_update: i64,
135
136 triggered: bool,
138}
139
140impl<T> WindowState<T> {
141 fn new(window: Window) -> Self {
142 Self {
143 window,
144 events: Vec::new(),
145 last_update: chrono::Utc::now().timestamp_millis(),
146 triggered: false,
147 }
148 }
149
150 fn add_event(&mut self, event: T) {
151 self.events.push(event);
152 self.last_update = chrono::Utc::now().timestamp_millis();
153 }
154
155 fn event_count(&self) -> usize {
156 self.events.len()
157 }
158}
159
160pub struct StreamExecutor<T>
194where
195 T: Clone + Send + Sync + 'static,
196{
197 pipeline: StreamPipeline,
199
200 input_tx: mpsc::Sender<T>,
202 input_rx: Arc<RwLock<mpsc::Receiver<T>>>,
203
204 output_tx: mpsc::Sender<ProcessorEvent<T>>,
206 output_rx: Arc<RwLock<mpsc::Receiver<ProcessorEvent<T>>>>,
207
208 watermark_generator: Arc<RwLock<Box<dyn WatermarkGenerator>>>,
210
211 window_states: Arc<DashMap<String, Vec<WindowState<T>>>>,
213
214 stats: Arc<RwLock<ExecutorStats>>,
216
217 running: Arc<AtomicU64>,
219
220 partition: u32,
222}
223
224impl<T> StreamExecutor<T>
225where
226 T: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de>,
227{
228 pub fn new(pipeline: StreamPipeline) -> Self {
230 let buffer_size = pipeline.config().processor.buffer_size;
231
232 let (input_tx, input_rx) = mpsc::channel(buffer_size);
233 let (output_tx, output_rx) = mpsc::channel(buffer_size);
234
235 let watermark_config = &pipeline.config().processor.watermark;
237 let watermark_generator: Box<dyn WatermarkGenerator> =
238 Box::new(BoundedOutOfOrdernessWatermark::new(
239 Duration::from_millis(watermark_config.max_delay_ms),
240 Some(Duration::from_millis(watermark_config.idle_timeout_ms)),
241 ));
242
243 Self {
244 pipeline,
245 input_tx,
246 input_rx: Arc::new(RwLock::new(input_rx)),
247 output_tx,
248 output_rx: Arc::new(RwLock::new(output_rx)),
249 watermark_generator: Arc::new(RwLock::new(watermark_generator)),
250 window_states: Arc::new(DashMap::new()),
251 stats: Arc::new(RwLock::new(ExecutorStats::new())),
252 running: Arc::new(AtomicU64::new(0)),
253 partition: 0,
254 }
255 }
256
257 pub async fn ingest(&self, event: T) -> Result<()> {
271 self.input_tx
272 .send(event)
273 .await
274 .map_err(|e| ProcessorError::Execution {
275 source: format!("Failed to ingest event: {}", e).into(),
276 })?;
277 Ok(())
278 }
279
280 pub async fn ingest_batch(&self, events: Vec<T>) -> Result<()> {
282 for event in events {
283 self.ingest(event).await?;
284 }
285 Ok(())
286 }
287
288 pub async fn next_output(&self) -> Option<ProcessorEvent<T>> {
290 let mut rx = self.output_rx.write().await;
291 rx.recv().await
292 }
293
294 pub async fn stats(&self) -> ExecutorStats {
296 self.stats.read().await.clone()
297 }
298
299 pub async fn current_watermark(&self) -> Watermark {
301 self.watermark_generator.read().await.current_watermark()
302 }
303
304 pub fn is_running(&self) -> bool {
306 self.running.load(Ordering::Relaxed) == 1
307 }
308
309 pub async fn run(&self) -> Result<()>
329 where
330 T: EventTimeExtractor<T> + KeyExtractor<T>,
331 {
332 info!(
333 pipeline = %self.pipeline.name(),
334 "Starting stream executor"
335 );
336
337 self.running.store(1, Ordering::Relaxed);
338
339 let watermark_interval = self.pipeline.config().processor.watermark.update_interval_ms;
341 self.spawn_watermark_updater(Duration::from_millis(watermark_interval));
342
343 loop {
345 if self.running.load(Ordering::Relaxed) == 0 {
346 info!("Executor stopped");
347 break;
348 }
349
350 let mut rx = self.input_rx.write().await;
351 match time::timeout(Duration::from_millis(100), rx.recv()).await {
352 Ok(Some(event)) => {
353 drop(rx); if let Err(e) = self.process_event(event).await {
355 error!(error = %e, "Failed to process event");
356 self.stats.write().await.inc_errors();
357 }
358 }
359 Ok(None) => {
360 debug!("Input channel closed");
361 break;
362 }
363 Err(_) => {
364 continue;
366 }
367 }
368 }
369
370 info!("Stream executor stopped");
371 Ok(())
372 }
373
374 pub fn stop(&self) {
376 info!("Stopping stream executor");
377 self.running.store(0, Ordering::Relaxed);
378 }
379
380 async fn process_event(&self, event: T) -> Result<()>
382 where
383 T: EventTimeExtractor<T> + KeyExtractor<T>,
384 {
385 trace!("Processing event");
386
387 let event_time = event.extract_event_time(&event);
389 let key = event.extract_key(&event);
390 let key_string = key.to_key_string();
391
392 let timestamp = event_time.timestamp_millis();
393
394 let mut wm_gen = self.watermark_generator.write().await;
396 let new_watermark = wm_gen.on_event(timestamp, self.partition);
397 drop(wm_gen);
398
399 if let Some(watermark) = new_watermark {
400 debug!(watermark = %watermark, "Watermark advanced");
401 self.stats.write().await.update_watermark(watermark.timestamp);
402
403 self.check_and_trigger_windows(watermark).await?;
405 }
406
407 let current_watermark = self.current_watermark().await;
409 if timestamp < current_watermark.timestamp {
410 let config = &self.pipeline.config().processor.window;
411 let lateness = current_watermark.timestamp - timestamp;
412
413 if lateness > config.allowed_lateness_ms as i64 {
414 if config.drop_late_events {
415 warn!(
416 lateness_ms = lateness,
417 "Dropping late event"
418 );
419 self.stats.write().await.inc_events_dropped();
420 return Ok(());
421 } else {
422 warn!(
423 lateness_ms = lateness,
424 "Processing late event"
425 );
426 }
427 }
428 }
429
430 let windows = self.assign_windows(timestamp, &key_string);
432
433 for window in windows {
435 self.add_event_to_window(&key_string, window, event.clone())
436 .await;
437 }
438
439 let mut stats = self.stats.write().await;
441 stats.inc_events_processed();
442 stats.add_bytes_processed(std::mem::size_of::<T>() as u64);
443 stats.set_active_windows(self.window_states.len() as u64);
444
445 Ok(())
446 }
447
448 fn assign_windows(&self, timestamp: i64, _key: &str) -> Vec<Window> {
450 let window_config = &self.pipeline.config().processor.window;
451 let dt = chrono::DateTime::from_timestamp_millis(timestamp)
452 .unwrap_or_else(chrono::Utc::now);
453
454 match window_config.window_type {
455 crate::config::WindowType::Tumbling => {
456 let size = chrono::Duration::milliseconds(window_config.size_ms.unwrap_or(60_000) as i64);
457 let assigner = TumblingWindowAssigner::new(size);
458 assigner.assign_windows(dt)
459 }
460 crate::config::WindowType::Sliding => {
461 let size = chrono::Duration::milliseconds(window_config.size_ms.unwrap_or(60_000) as i64);
462 let slide = chrono::Duration::milliseconds(window_config.slide_ms.unwrap_or(30_000) as i64);
463 let assigner = SlidingWindowAssigner::new(size, slide);
464 assigner.assign_windows(dt)
465 }
466 crate::config::WindowType::Session => {
467 let gap = chrono::Duration::milliseconds(window_config.gap_ms.unwrap_or(30_000) as i64);
468 let assigner = SessionWindowAssigner::new(gap);
469 assigner.assign_windows(dt)
470 }
471 crate::config::WindowType::Count => {
472 let start = chrono::DateTime::from_timestamp(i64::MIN / 1000, 0).unwrap_or(chrono::DateTime::UNIX_EPOCH);
474 let end = chrono::DateTime::from_timestamp(i64::MAX / 1000, 0).unwrap_or(chrono::DateTime::from_timestamp(253402300799, 999999999).unwrap());
475 let bounds = crate::window::WindowBounds::new(start, end);
476 vec![Window::new(bounds)]
477 }
478 crate::config::WindowType::Global => {
479 let start = chrono::DateTime::from_timestamp(i64::MIN / 1000, 0).unwrap_or(chrono::DateTime::UNIX_EPOCH);
480 let end = chrono::DateTime::from_timestamp(i64::MAX / 1000, 0).unwrap_or(chrono::DateTime::from_timestamp(253402300799, 999999999).unwrap());
481 let bounds = crate::window::WindowBounds::new(start, end);
482 vec![Window::new(bounds)]
483 }
484 }
485 }
486
487 async fn add_event_to_window(&self, key: &str, window: Window, event: T) {
489 let mut windows = self
490 .window_states
491 .entry(key.to_string())
492 .or_insert_with(Vec::new);
493
494 let window_state = windows
496 .iter_mut()
497 .find(|ws| ws.window == window);
498
499 match window_state {
500 Some(ws) => {
501 ws.add_event(event);
502 trace!(
503 key = key,
504 window = ?window,
505 event_count = ws.event_count(),
506 "Added event to existing window"
507 );
508 }
509 None => {
510 let mut ws = WindowState::new(window.clone());
511 ws.add_event(event);
512 windows.push(ws);
513 self.stats.write().await.inc_windows_created();
514 trace!(
515 key = key,
516 window = ?window,
517 "Created new window"
518 );
519 }
520 }
521 }
522
523 async fn check_and_trigger_windows(&self, watermark: Watermark) -> Result<()> {
525 let mut windows_to_trigger = Vec::new();
526
527 for entry in self.window_states.iter() {
529 let key = entry.key().clone();
530 let windows = entry.value();
531
532 for (idx, window_state) in windows.iter().enumerate() {
533 let window_end_ms = window_state.window.bounds.end.timestamp_millis();
534 if !window_state.triggered && window_end_ms <= watermark.timestamp {
535 windows_to_trigger.push((key.clone(), idx, window_state.window.clone()));
536 }
537 }
538 }
539
540 for (key, idx, window) in windows_to_trigger {
542 self.trigger_window(&key, idx, watermark).await?;
543 }
544
545 Ok(())
546 }
547
548 async fn trigger_window(
550 &self,
551 key: &str,
552 window_idx: usize,
553 watermark: Watermark,
554 ) -> Result<()> {
555 debug!(
556 key = key,
557 window_idx = window_idx,
558 watermark = %watermark,
559 "Triggering window"
560 );
561
562 let mut window_states = self
564 .window_states
565 .get_mut(key)
566 .ok_or_else(|| ProcessorError::Execution {
567 source: format!("Window state not found for key: {}", key).into(),
568 })?;
569
570 if window_idx >= window_states.len() {
571 return Err(ProcessorError::Execution {
572 source: format!("Invalid window index: {}", window_idx).into(),
573 });
574 }
575
576 let window_state = &mut window_states[window_idx];
577 window_state.triggered = true;
578
579 for event in &window_state.events {
582 let processor_event = ProcessorEvent::new(
583 event.clone(),
584 window_state.window.bounds.start,
585 key.to_string(),
586 );
587
588 self.output_tx
589 .send(processor_event)
590 .await
591 .map_err(|e| ProcessorError::Execution {
592 source: format!("Failed to emit result: {}", e).into(),
593 })?;
594 }
595
596 self.stats.write().await.inc_windows_triggered();
597
598 trace!(
601 key = key,
602 event_count = window_state.events.len(),
603 "Window triggered and results emitted"
604 );
605
606 Ok(())
607 }
608
609 fn spawn_watermark_updater(&self, interval: Duration) {
611 let watermark_generator = Arc::clone(&self.watermark_generator);
612 let running = Arc::clone(&self.running);
613 let stats = Arc::clone(&self.stats);
614
615 tokio::spawn(async move {
616 let mut ticker = time::interval(interval);
617
618 while running.load(Ordering::Relaxed) == 1 {
619 ticker.tick().await;
620
621 let mut gen = watermark_generator.write().await;
622 if let Some(watermark) = gen.on_periodic_check() {
623 debug!(watermark = %watermark, "Periodic watermark update");
624 stats.write().await.update_watermark(watermark.timestamp);
625 }
626 }
627
628 debug!("Watermark updater stopped");
629 });
630 }
631
632 pub fn active_window_count(&self) -> usize {
634 self.window_states.len()
635 }
636
637 pub fn clear_windows(&self) {
639 self.window_states.clear();
640 }
641}
642
643impl<T> Drop for StreamExecutor<T>
644where
645 T: Clone + Send + Sync,
646{
647 fn drop(&mut self) {
648 self.running.store(0, Ordering::Relaxed);
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656 use crate::config::WindowConfig;
657 use crate::core::{EventKey, EventTimeExtractor, KeyExtractor};
658 use crate::pipeline::StreamPipelineBuilder;
659 use chrono::{DateTime, Utc};
660
661 #[derive(Debug, Clone, Serialize, Deserialize)]
662 struct TestEvent {
663 value: f64,
664 timestamp: i64,
665 key: String,
666 }
667
668 impl EventTimeExtractor<TestEvent> for TestEvent {
669 fn extract_event_time(&self, _event: &TestEvent) -> DateTime<Utc> {
670 DateTime::from_timestamp_millis(self.timestamp).unwrap_or_else(Utc::now)
671 }
672 }
673
674 impl KeyExtractor<TestEvent> for TestEvent {
675 fn extract_key(&self, event: &TestEvent) -> EventKey {
676 EventKey::Session(event.key.clone())
677 }
678 }
679
680 #[tokio::test]
681 async fn test_executor_creation() {
682 let pipeline = StreamPipelineBuilder::new()
683 .with_name("test")
684 .build()
685 .unwrap();
686
687 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
688 assert!(!executor.is_running());
689 }
690
691 #[tokio::test]
692 async fn test_executor_ingest() {
693 let pipeline = StreamPipelineBuilder::new()
694 .with_name("test")
695 .build()
696 .unwrap();
697
698 let executor = pipeline.create_executor();
699
700 let event = TestEvent {
701 value: 42.0,
702 timestamp: Utc::now().timestamp_millis(),
703 key: "test-key".to_string(),
704 };
705
706 executor.ingest(event).await.unwrap();
707 }
708
709 #[tokio::test]
710 async fn test_executor_ingest_batch() {
711 let pipeline = StreamPipelineBuilder::new()
712 .with_name("test")
713 .build()
714 .unwrap();
715
716 let executor = pipeline.create_executor();
717
718 let events = vec![
719 TestEvent {
720 value: 1.0,
721 timestamp: Utc::now().timestamp_millis(),
722 key: "key1".to_string(),
723 },
724 TestEvent {
725 value: 2.0,
726 timestamp: Utc::now().timestamp_millis(),
727 key: "key2".to_string(),
728 },
729 ];
730
731 executor.ingest_batch(events).await.unwrap();
732 }
733
734 #[tokio::test]
735 async fn test_executor_stats() {
736 let pipeline = StreamPipelineBuilder::new()
737 .with_name("test")
738 .build()
739 .unwrap();
740
741 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
742
743 let stats = executor.stats().await;
744 assert_eq!(stats.events_processed, 0);
745 assert_eq!(stats.events_dropped, 0);
746 }
747
748 #[tokio::test]
749 async fn test_executor_watermark() {
750 let pipeline = StreamPipelineBuilder::new()
751 .with_name("test")
752 .build()
753 .unwrap();
754
755 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
756
757 let watermark = executor.current_watermark().await;
758 assert!(watermark.is_min());
759 }
760
761 #[test]
762 fn test_executor_stats_methods() {
763 let mut stats = ExecutorStats::new();
764
765 stats.inc_events_processed();
766 assert_eq!(stats.events_processed, 1);
767
768 stats.inc_events_dropped();
769 assert_eq!(stats.events_dropped, 1);
770
771 stats.inc_windows_created();
772 assert_eq!(stats.windows_created, 1);
773
774 stats.inc_windows_triggered();
775 assert_eq!(stats.windows_triggered, 1);
776
777 stats.inc_aggregations_computed();
778 assert_eq!(stats.aggregations_computed, 1);
779
780 stats.update_watermark(12345);
781 assert_eq!(stats.current_watermark, 12345);
782
783 stats.set_active_windows(10);
784 assert_eq!(stats.active_windows, 10);
785
786 stats.add_bytes_processed(1024);
787 assert_eq!(stats.bytes_processed, 1024);
788
789 stats.inc_errors();
790 assert_eq!(stats.errors, 1);
791 }
792
793 #[test]
794 fn test_executor_stats_metrics() {
795 let mut stats = ExecutorStats::new();
796 stats.events_processed = 1000;
797 stats.bytes_processed = 10_485_760; let eps = stats.events_per_second(10.0);
800 assert_eq!(eps, 100.0);
801
802 let mbps = stats.throughput_mbps(10.0);
803 assert_eq!(mbps, 1.0);
804 }
805
806 #[tokio::test]
807 async fn test_executor_stop() {
808 let pipeline = StreamPipelineBuilder::new()
809 .with_name("test")
810 .build()
811 .unwrap();
812
813 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
814
815 executor.running.store(1, Ordering::Relaxed);
816 assert!(executor.is_running());
817
818 executor.stop();
819 assert!(!executor.is_running());
820 }
821
822 #[tokio::test]
823 async fn test_assign_tumbling_windows() {
824 let pipeline = StreamPipelineBuilder::new()
825 .with_name("test")
826 .with_tumbling_window(60_000)
827 .build()
828 .unwrap();
829
830 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
831
832 let timestamp = 125_000; let windows = executor.assign_windows(timestamp, "test-key");
834
835 assert_eq!(windows.len(), 1);
836 assert_eq!(windows[0].bounds.start.timestamp_millis(), 120_000); assert_eq!(windows[0].bounds.end.timestamp_millis(), 180_000); }
839
840 #[tokio::test]
841 async fn test_assign_sliding_windows() {
842 let pipeline = StreamPipelineBuilder::new()
843 .with_name("test")
844 .with_sliding_window(120_000, 60_000)
845 .build()
846 .unwrap();
847
848 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
849
850 let timestamp = 125_000;
851 let windows = executor.assign_windows(timestamp, "test-key");
852
853 assert!(windows.len() >= 1);
854 }
855
856 #[tokio::test]
857 async fn test_window_state() {
858 let start = chrono::DateTime::from_timestamp(0, 0).unwrap();
859 let end = chrono::DateTime::from_timestamp(60, 0).unwrap();
860 let bounds = crate::window::WindowBounds::new(start, end);
861 let window = Window::new(bounds);
862 let mut state = WindowState::<i32>::new(window);
863
864 assert_eq!(state.event_count(), 0);
865 assert!(!state.triggered);
866
867 state.add_event(42);
868 assert_eq!(state.event_count(), 1);
869 assert_eq!(state.events[0], 42);
870 }
871
872 #[tokio::test]
873 async fn test_add_event_to_window() {
874 let pipeline = StreamPipelineBuilder::new()
875 .with_name("test")
876 .build()
877 .unwrap();
878
879 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
880
881 let start = chrono::DateTime::from_timestamp(0, 0).unwrap();
882 let end = chrono::DateTime::from_timestamp(60, 0).unwrap();
883 let bounds = crate::window::WindowBounds::new(start, end);
884 let window = Window::new(bounds);
885 let event = TestEvent {
886 value: 42.0,
887 timestamp: 30_000,
888 key: "test".to_string(),
889 };
890
891 executor.add_event_to_window("test-key", window, event).await;
892
893 assert_eq!(executor.active_window_count(), 1);
894 }
895
896 #[tokio::test]
897 async fn test_clear_windows() {
898 let pipeline = StreamPipelineBuilder::new()
899 .with_name("test")
900 .build()
901 .unwrap();
902
903 let executor: StreamExecutor<TestEvent> = pipeline.create_executor();
904
905 let start = chrono::DateTime::from_timestamp(0, 0).unwrap();
906 let end = chrono::DateTime::from_timestamp(60, 0).unwrap();
907 let bounds = crate::window::WindowBounds::new(start, end);
908 let window = Window::new(bounds);
909 let event = TestEvent {
910 value: 42.0,
911 timestamp: 30_000,
912 key: "test".to_string(),
913 };
914
915 executor.add_event_to_window("test-key", window, event).await;
916 assert_eq!(executor.active_window_count(), 1);
917
918 executor.clear_windows();
919 assert_eq!(executor.active_window_count(), 0);
920 }
921}