Skip to main content

llm_optimizer_processor/pipeline/
executor.rs

1//! Stream executor for running stream processing pipelines
2//!
3//! This module provides the execution engine for stream processing pipelines,
4//! handling event ingestion, watermark propagation, window management, and result emission.
5
6use 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/// Statistics for the stream executor
24#[derive(Debug, Clone, Serialize, Deserialize, Default)]
25pub struct ExecutorStats {
26    /// Total events processed
27    pub events_processed: u64,
28
29    /// Total events dropped (late events)
30    pub events_dropped: u64,
31
32    /// Total windows created
33    pub windows_created: u64,
34
35    /// Total windows triggered
36    pub windows_triggered: u64,
37
38    /// Total aggregations computed
39    pub aggregations_computed: u64,
40
41    /// Current watermark timestamp
42    pub current_watermark: i64,
43
44    /// Number of active windows
45    pub active_windows: u64,
46
47    /// Total bytes processed
48    pub bytes_processed: u64,
49
50    /// Processing errors
51    pub errors: u64,
52}
53
54impl ExecutorStats {
55    /// Create new empty stats
56    pub fn new() -> Self {
57        Self::default()
58    }
59
60    /// Increment events processed
61    pub fn inc_events_processed(&mut self) {
62        self.events_processed += 1;
63    }
64
65    /// Increment events dropped
66    pub fn inc_events_dropped(&mut self) {
67        self.events_dropped += 1;
68    }
69
70    /// Increment windows created
71    pub fn inc_windows_created(&mut self) {
72        self.windows_created += 1;
73    }
74
75    /// Increment windows triggered
76    pub fn inc_windows_triggered(&mut self) {
77        self.windows_triggered += 1;
78    }
79
80    /// Increment aggregations computed
81    pub fn inc_aggregations_computed(&mut self) {
82        self.aggregations_computed += 1;
83    }
84
85    /// Update watermark
86    pub fn update_watermark(&mut self, watermark: i64) {
87        self.current_watermark = watermark;
88    }
89
90    /// Set active windows count
91    pub fn set_active_windows(&mut self, count: u64) {
92        self.active_windows = count;
93    }
94
95    /// Add bytes processed
96    pub fn add_bytes_processed(&mut self, bytes: u64) {
97        self.bytes_processed += bytes;
98    }
99
100    /// Increment errors
101    pub fn inc_errors(&mut self) {
102        self.errors += 1;
103    }
104
105    /// Get events per second
106    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    /// Get throughput in MB/s
115    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/// Window state for tracking window lifecycle
125#[derive(Debug, Clone)]
126struct WindowState<T> {
127    /// The window
128    window: Window,
129
130    /// Events in this window
131    events: Vec<T>,
132
133    /// Last update timestamp
134    last_update: i64,
135
136    /// Whether the window has been triggered
137    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
160/// Stream executor for processing events through a pipeline
161///
162/// The executor handles:
163/// - Event ingestion from sources
164/// - Watermark generation and propagation
165/// - Window assignment and lifecycle management
166/// - Trigger evaluation
167/// - Aggregation computation
168/// - Result emission to sinks
169///
170/// # Example
171///
172/// ```rust,no_run
173/// use processor::pipeline::{StreamPipelineBuilder, StreamExecutor};
174/// use processor::config::WindowConfig;
175///
176/// # async fn example() -> anyhow::Result<()> {
177/// let pipeline = StreamPipelineBuilder::new()
178///     .with_name("example")
179///     .with_tumbling_window(60_000)
180///     .build()?;
181///
182/// let mut executor = pipeline.create_executor::<f64>();
183///
184/// // Ingest events
185/// executor.ingest(42.0).await?;
186/// executor.ingest(100.0).await?;
187///
188/// // Process and emit results
189/// // executor.run().await?;
190/// # Ok(())
191/// # }
192/// ```
193pub struct StreamExecutor<T>
194where
195    T: Clone + Send + Sync + 'static,
196{
197    /// The pipeline configuration
198    pipeline: StreamPipeline,
199
200    /// Event input channel
201    input_tx: mpsc::Sender<T>,
202    input_rx: Arc<RwLock<mpsc::Receiver<T>>>,
203
204    /// Output channel
205    output_tx: mpsc::Sender<ProcessorEvent<T>>,
206    output_rx: Arc<RwLock<mpsc::Receiver<ProcessorEvent<T>>>>,
207
208    /// Watermark generator
209    watermark_generator: Arc<RwLock<Box<dyn WatermarkGenerator>>>,
210
211    /// Window states per key
212    window_states: Arc<DashMap<String, Vec<WindowState<T>>>>,
213
214    /// Execution statistics
215    stats: Arc<RwLock<ExecutorStats>>,
216
217    /// Running flag
218    running: Arc<AtomicU64>,
219
220    /// Partition ID
221    partition: u32,
222}
223
224impl<T> StreamExecutor<T>
225where
226    T: Clone + Send + Sync + 'static + Serialize + for<'de> Deserialize<'de>,
227{
228    /// Create a new stream executor
229    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        // Create watermark generator based on configuration
236        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    /// Ingest an event into the pipeline
258    ///
259    /// # Example
260    ///
261    /// ```rust,no_run
262    /// # use processor::pipeline::{StreamPipelineBuilder, StreamExecutor};
263    /// # async fn example() -> anyhow::Result<()> {
264    /// # let pipeline = StreamPipelineBuilder::new().build()?;
265    /// let mut executor = pipeline.create_executor::<f64>();
266    /// executor.ingest(42.5).await?;
267    /// # Ok(())
268    /// # }
269    /// ```
270    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    /// Ingest a batch of events
281    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    /// Get the next output event
289    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    /// Get current statistics
295    pub async fn stats(&self) -> ExecutorStats {
296        self.stats.read().await.clone()
297    }
298
299    /// Get current watermark
300    pub async fn current_watermark(&self) -> Watermark {
301        self.watermark_generator.read().await.current_watermark()
302    }
303
304    /// Check if the executor is running
305    pub fn is_running(&self) -> bool {
306        self.running.load(Ordering::Relaxed) == 1
307    }
308
309    /// Run the executor (process events continuously)
310    ///
311    /// This will process events from the input channel until stopped.
312    ///
313    /// # Example
314    ///
315    /// ```rust,no_run
316    /// # use processor::pipeline::{StreamPipelineBuilder, StreamExecutor};
317    /// # async fn example() -> anyhow::Result<()> {
318    /// # let pipeline = StreamPipelineBuilder::new().build()?;
319    /// let executor = pipeline.create_executor::<f64>();
320    ///
321    /// // Run in a background task
322    /// tokio::spawn(async move {
323    ///     // executor.run().await.unwrap();
324    /// });
325    /// # Ok(())
326    /// # }
327    /// ```
328    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        // Spawn watermark update task
340        let watermark_interval = self.pipeline.config().processor.watermark.update_interval_ms;
341        self.spawn_watermark_updater(Duration::from_millis(watermark_interval));
342
343        // Process events
344        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); // Release the lock
354                    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                    // Timeout - continue
365                    continue;
366                }
367            }
368        }
369
370        info!("Stream executor stopped");
371        Ok(())
372    }
373
374    /// Stop the executor
375    pub fn stop(&self) {
376        info!("Stopping stream executor");
377        self.running.store(0, Ordering::Relaxed);
378    }
379
380    /// Process a single event
381    async fn process_event(&self, event: T) -> Result<()>
382    where
383        T: EventTimeExtractor<T> + KeyExtractor<T>,
384    {
385        trace!("Processing event");
386
387        // Extract event time and key
388        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        // Update watermark
395        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            // Check and trigger windows
404            self.check_and_trigger_windows(watermark).await?;
405        }
406
407        // Check if event is late
408        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        // Assign to windows
431        let windows = self.assign_windows(timestamp, &key_string);
432
433        // Add event to windows
434        for window in windows {
435            self.add_event_to_window(&key_string, window, event.clone())
436                .await;
437        }
438
439        // Update stats
440        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    /// Assign event to windows
449    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                // For count windows, use a single global window
473                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    /// Add event to a window
488    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        // Find or create window state
495        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    /// Check and trigger windows based on watermark
524    async fn check_and_trigger_windows(&self, watermark: Watermark) -> Result<()> {
525        let mut windows_to_trigger = Vec::new();
526
527        // Find windows that should be triggered
528        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        // Trigger windows
541        for (key, idx, window) in windows_to_trigger {
542            self.trigger_window(&key, idx, watermark).await?;
543        }
544
545        Ok(())
546    }
547
548    /// Trigger a window and emit results
549    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        // Get window state
563        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 now, just emit all events in the window
580        // In a full implementation, this would apply aggregations
581        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        // Clean up triggered windows (optional: keep for late events)
599        // For now, we keep them
600        trace!(
601            key = key,
602            event_count = window_state.events.len(),
603            "Window triggered and results emitted"
604        );
605
606        Ok(())
607    }
608
609    /// Spawn a background task to periodically update watermarks
610    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    /// Get the number of active windows
633    pub fn active_window_count(&self) -> usize {
634        self.window_states.len()
635    }
636
637    /// Clear all window states
638    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        // Stop the executor by setting the running flag to 0
649        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; // 10 MB
798
799        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; // 2 minutes and 5 seconds
833        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); // Start of 3rd minute
837        assert_eq!(windows[0].bounds.end.timestamp_millis(), 180_000); // End of 3rd minute
838    }
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}