Skip to main content

llm_optimizer_processor/window/
manager.rs

1//! Window lifecycle management
2//!
3//! This module provides the `WindowManager` for managing the complete lifecycle of windows:
4//! - Creating windows based on event timestamps
5//! - Tracking active windows and their states
6//! - Triggering window evaluation based on watermarks
7//! - Cleaning up completed windows
8//! - Merging session windows
9//!
10//! # Example
11//!
12//! ```rust
13//! use processor::window::{
14//!     WindowManager, TumblingWindowAssigner, OnWatermarkTrigger,
15//! };
16//! use processor::aggregation::{CompositeAggregator, AggregationResult};
17//! use chrono::{Duration, Utc};
18//!
19//! # fn example() -> anyhow::Result<()> {
20//! // Create a window manager with 5-minute tumbling windows
21//! let assigner = TumblingWindowAssigner::new(Duration::minutes(5));
22//! let trigger = OnWatermarkTrigger::new();
23//! let mut manager = WindowManager::new(assigner, trigger);
24//!
25//! // Process events
26//! let event_time = Utc::now();
27//! let windows = manager.assign_event(event_time)?;
28//!
29//! // Check which windows should fire based on watermark
30//! let watermark = Utc::now();
31//! let ready_windows = manager.check_triggers(watermark)?;
32//! # Ok(())
33//! # }
34//! ```
35
36use super::{Window, WindowAssigner, WindowTrigger, TriggerContext, TriggerResult};
37use crate::error::{ProcessorError, WindowError};
38use chrono::{DateTime, Utc};
39use dashmap::DashMap;
40use serde::{Deserialize, Serialize};
41use std::collections::{BTreeMap, HashMap};
42use std::sync::Arc;
43use tracing::{debug, trace, warn};
44
45/// Statistics tracked for each window
46#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47pub struct WindowStats {
48    /// Number of events assigned to this window
49    pub event_count: u64,
50    /// Timestamp when the window was first created
51    pub created_at: DateTime<Utc>,
52    /// Timestamp when the window was last updated
53    pub last_update: DateTime<Utc>,
54    /// Timestamp when the window was closed (if closed)
55    pub closed_at: Option<DateTime<Utc>>,
56    /// Minimum event timestamp seen
57    pub min_event_time: Option<DateTime<Utc>>,
58    /// Maximum event timestamp seen
59    pub max_event_time: Option<DateTime<Utc>>,
60}
61
62impl WindowStats {
63    /// Create new window statistics
64    pub fn new() -> Self {
65        let now = Utc::now();
66        Self {
67            event_count: 0,
68            created_at: now,
69            last_update: now,
70            closed_at: None,
71            min_event_time: None,
72            max_event_time: None,
73        }
74    }
75
76    /// Update statistics with a new event
77    pub fn record_event(&mut self, event_time: DateTime<Utc>) {
78        self.event_count += 1;
79        self.last_update = Utc::now();
80
81        self.min_event_time = Some(
82            self.min_event_time
83                .map(|t| t.min(event_time))
84                .unwrap_or(event_time)
85        );
86
87        self.max_event_time = Some(
88            self.max_event_time
89                .map(|t| t.max(event_time))
90                .unwrap_or(event_time)
91        );
92    }
93
94    /// Mark the window as closed
95    pub fn close(&mut self) {
96        self.closed_at = Some(Utc::now());
97    }
98
99    /// Check if the window is closed
100    pub fn is_closed(&self) -> bool {
101        self.closed_at.is_some()
102    }
103}
104
105/// Metadata for an active window
106#[derive(Debug, Clone)]
107pub struct WindowMetadata {
108    /// The window definition
109    pub window: Window,
110    /// Statistics for this window
111    pub stats: WindowStats,
112    /// Whether this window has been triggered
113    pub triggered: bool,
114}
115
116impl WindowMetadata {
117    /// Create new window metadata
118    pub fn new(window: Window) -> Self {
119        Self {
120            window,
121            stats: WindowStats::new(),
122            triggered: false,
123        }
124    }
125
126    /// Update with a new event
127    pub fn record_event(&mut self, event_time: DateTime<Utc>) {
128        self.stats.record_event(event_time);
129    }
130
131    /// Check if the window should be triggered
132    pub fn check_trigger<T: WindowTrigger>(
133        &mut self,
134        trigger: &mut T,
135        watermark: Option<DateTime<Utc>>,
136    ) -> TriggerResult {
137        if self.triggered {
138            return TriggerResult::Continue;
139        }
140
141        let ctx = TriggerContext {
142            window: self.window.clone(),
143            event_time: self.stats.max_event_time,
144            processing_time: Utc::now(),
145            watermark,
146            element_count: self.stats.event_count as usize,
147        };
148
149        let result = if let Some(wm) = watermark {
150            trigger.on_watermark(&ctx)
151        } else {
152            trigger.on_element(&ctx)
153        };
154
155        if result == TriggerResult::Fire || result == TriggerResult::FireAndPurge {
156            self.triggered = true;
157        }
158
159        result
160    }
161
162    /// Close this window
163    pub fn close(&mut self) {
164        self.stats.close();
165    }
166}
167
168/// Manager for window lifecycles
169///
170/// The `WindowManager` coordinates all aspects of window management:
171/// - Assigns events to appropriate windows
172/// - Tracks active windows and their states
173/// - Evaluates triggers to determine when windows should fire
174/// - Handles window merging for session windows
175/// - Cleans up completed windows
176pub struct WindowManager<A, T>
177where
178    A: WindowAssigner,
179    T: WindowTrigger,
180{
181    /// Window assigner for determining event-to-window mapping
182    assigner: A,
183    /// Trigger for determining when windows should fire
184    trigger: T,
185    /// Active windows indexed by window ID
186    active_windows: Arc<DashMap<String, WindowMetadata>>,
187    /// Windows ordered by end time for efficient watermark processing
188    windows_by_end_time: BTreeMap<i64, Vec<String>>,
189    /// Current watermark
190    current_watermark: Option<DateTime<Utc>>,
191    /// Configuration
192    allow_late_events: bool,
193    late_event_threshold: chrono::Duration,
194}
195
196impl<A, T> WindowManager<A, T>
197where
198    A: WindowAssigner,
199    T: WindowTrigger,
200{
201    /// Create a new window manager
202    pub fn new(assigner: A, trigger: T) -> Self {
203        Self {
204            assigner,
205            trigger,
206            active_windows: Arc::new(DashMap::new()),
207            windows_by_end_time: BTreeMap::new(),
208            current_watermark: None,
209            allow_late_events: true,
210            late_event_threshold: chrono::Duration::minutes(5),
211        }
212    }
213
214    /// Create a window manager with custom late event handling
215    pub fn with_late_events(
216        assigner: A,
217        trigger: T,
218        allow_late_events: bool,
219        late_event_threshold: chrono::Duration,
220    ) -> Self {
221        Self {
222            assigner,
223            trigger,
224            active_windows: Arc::new(DashMap::new()),
225            windows_by_end_time: BTreeMap::new(),
226            current_watermark: None,
227            allow_late_events,
228            late_event_threshold,
229        }
230    }
231
232    /// Assign an event to windows and update window metadata
233    ///
234    /// Returns the list of windows that this event belongs to.
235    pub fn assign_event(
236        &mut self,
237        event_time: DateTime<Utc>,
238    ) -> Result<Vec<Window>, ProcessorError> {
239        // Check for late events
240        if let Some(watermark) = self.current_watermark {
241            if event_time < watermark {
242                let late_by = watermark - event_time;
243
244                if !self.allow_late_events {
245                    return Err(WindowError::LateEvent {
246                        event_time: event_time.timestamp_millis(),
247                        watermark: watermark.timestamp_millis(),
248                        late_by: late_by.num_milliseconds(),
249                    }.into());
250                }
251
252                if late_by > self.late_event_threshold {
253                    warn!(
254                        event_time = %event_time,
255                        watermark = %watermark,
256                        late_by_ms = late_by.num_milliseconds(),
257                        "Event is too late, dropping"
258                    );
259                    return Ok(Vec::new());
260                }
261
262                debug!(
263                    event_time = %event_time,
264                    watermark = %watermark,
265                    late_by_ms = late_by.num_milliseconds(),
266                    "Processing late event"
267                );
268            }
269        }
270
271        // Assign event to windows
272        let windows = self.assigner.assign_windows(event_time);
273
274        trace!(
275            event_time = %event_time,
276            window_count = windows.len(),
277            "Assigned event to windows"
278        );
279
280        // Update or create window metadata
281        for window in &windows {
282            let mut metadata = self.active_windows
283                .entry(window.id.clone())
284                .or_insert_with(|| {
285                    // Track window by end time
286                    let end_millis = window.bounds.end.timestamp_millis();
287                    self.windows_by_end_time
288                        .entry(end_millis)
289                        .or_insert_with(Vec::new)
290                        .push(window.id.clone());
291
292                    debug!(
293                        window_id = %window.id,
294                        window_start = %window.bounds.start,
295                        window_end = %window.bounds.end,
296                        "Created new window"
297                    );
298
299                    WindowMetadata::new(window.clone())
300                });
301
302            metadata.record_event(event_time);
303        }
304
305        Ok(windows)
306    }
307
308    /// Update the watermark and check which windows are ready to fire
309    ///
310    /// Returns a list of window IDs that should be evaluated.
311    pub fn advance_watermark(
312        &mut self,
313        watermark: DateTime<Utc>,
314    ) -> Result<Vec<String>, ProcessorError> {
315        // Ensure watermark doesn't go backwards
316        if let Some(current) = self.current_watermark {
317            if watermark < current {
318                warn!(
319                    current = %current,
320                    new_watermark = %watermark,
321                    "Watermark went backwards, ignoring"
322                );
323                return Ok(Vec::new());
324            }
325        }
326
327        debug!(
328            old_watermark = ?self.current_watermark,
329            new_watermark = %watermark,
330            "Advancing watermark"
331        );
332
333        self.current_watermark = Some(watermark);
334
335        // Find windows that should fire based on watermark
336        let mut ready_windows = Vec::new();
337        let watermark_millis = watermark.timestamp_millis();
338
339        // Check all windows with end time <= watermark
340        let ended_windows: Vec<_> = self.windows_by_end_time
341            .range(..=watermark_millis)
342            .flat_map(|(_, window_ids)| window_ids.clone())
343            .collect();
344
345        for window_id in ended_windows {
346            if let Some(mut metadata) = self.active_windows.get_mut(&window_id) {
347                let result = metadata.check_trigger(&mut self.trigger, Some(watermark));
348
349                match result {
350                    TriggerResult::Fire | TriggerResult::FireAndPurge => {
351                        debug!(
352                            window_id = %window_id,
353                            trigger_result = ?result,
354                            "Window ready to fire"
355                        );
356                        ready_windows.push(window_id.clone());
357                    }
358                    _ => {}
359                }
360            }
361        }
362
363        Ok(ready_windows)
364    }
365
366    /// Get metadata for a specific window
367    pub fn get_window(&self, window_id: &str) -> Option<WindowMetadata> {
368        self.active_windows.get(window_id).map(|m| m.clone())
369    }
370
371    /// Remove and return a window (for cleanup after processing)
372    pub fn remove_window(&mut self, window_id: &str) -> Option<WindowMetadata> {
373        // Remove from active windows
374        let metadata = self.active_windows.remove(window_id).map(|(_, m)| m)?;
375
376        // Remove from end time index
377        let end_millis = metadata.window.bounds.end.timestamp_millis();
378        if let Some(window_ids) = self.windows_by_end_time.get_mut(&end_millis) {
379            window_ids.retain(|id| id != window_id);
380            if window_ids.is_empty() {
381                self.windows_by_end_time.remove(&end_millis);
382            }
383        }
384
385        debug!(
386            window_id = %window_id,
387            "Removed window"
388        );
389
390        Some(metadata)
391    }
392
393    /// Get all active window IDs
394    pub fn active_window_ids(&self) -> Vec<String> {
395        self.active_windows.iter().map(|e| e.key().clone()).collect()
396    }
397
398    /// Get the number of active windows
399    pub fn active_window_count(&self) -> usize {
400        self.active_windows.len()
401    }
402
403    /// Get the current watermark
404    pub fn current_watermark(&self) -> Option<DateTime<Utc>> {
405        self.current_watermark
406    }
407
408    /// Clean up old windows that have been triggered and are beyond retention
409    ///
410    /// Returns the number of windows removed.
411    pub fn cleanup_old_windows(
412        &mut self,
413        retention_duration: chrono::Duration,
414    ) -> usize {
415        let cutoff = Utc::now() - retention_duration;
416        let mut removed_count = 0;
417
418        // Find windows to remove
419        let to_remove: Vec<String> = self.active_windows
420            .iter()
421            .filter(|entry| {
422                let metadata = entry.value();
423                metadata.triggered && metadata.window.bounds.end < cutoff
424            })
425            .map(|entry| entry.key().clone())
426            .collect();
427
428        // Remove them
429        for window_id in to_remove {
430            if self.remove_window(&window_id).is_some() {
431                removed_count += 1;
432            }
433        }
434
435        if removed_count > 0 {
436            debug!(
437                removed_count,
438                cutoff = %cutoff,
439                "Cleaned up old windows"
440            );
441        }
442
443        removed_count
444    }
445
446    /// Get statistics for all active windows
447    pub fn get_statistics(&self) -> HashMap<String, WindowStats> {
448        self.active_windows
449            .iter()
450            .map(|entry| (entry.key().clone(), entry.value().stats.clone()))
451            .collect()
452    }
453
454    /// Reset the manager (clear all windows)
455    pub fn reset(&mut self) {
456        self.active_windows.clear();
457        self.windows_by_end_time.clear();
458        self.current_watermark = None;
459        debug!("Window manager reset");
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466    use crate::window::{TumblingWindowAssigner, SlidingWindowAssigner, OnWatermarkTrigger};
467    use chrono::{Duration, TimeZone};
468
469    fn create_timestamp(millis: i64) -> DateTime<Utc> {
470        Utc.timestamp_millis_opt(millis).unwrap()
471    }
472
473    #[test]
474    fn test_window_manager_basic() {
475        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
476        let trigger = OnWatermarkTrigger::new();
477        let mut manager = WindowManager::new(assigner, trigger);
478
479        // Assign event at 500ms
480        let windows = manager.assign_event(create_timestamp(500)).unwrap();
481        assert_eq!(windows.len(), 1);
482        assert_eq!(manager.active_window_count(), 1);
483
484        // Check the window
485        let window_id = &windows[0].id;
486        let metadata = manager.get_window(window_id).unwrap();
487        assert_eq!(metadata.stats.event_count, 1);
488        assert!(!metadata.triggered);
489    }
490
491    #[test]
492    fn test_window_manager_watermark_trigger() {
493        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
494        let trigger = OnWatermarkTrigger::new();
495        let mut manager = WindowManager::new(assigner, trigger);
496
497        // Assign events
498        manager.assign_event(create_timestamp(100)).unwrap();
499        manager.assign_event(create_timestamp(500)).unwrap();
500        manager.assign_event(create_timestamp(900)).unwrap();
501
502        // Advance watermark to trigger window
503        let ready = manager.advance_watermark(create_timestamp(1000)).unwrap();
504        assert_eq!(ready.len(), 1);
505
506        // Check window is marked as triggered
507        let window_id = &ready[0];
508        let metadata = manager.get_window(window_id).unwrap();
509        assert!(metadata.triggered);
510        assert_eq!(metadata.stats.event_count, 3);
511    }
512
513    #[test]
514    fn test_window_manager_multiple_windows() {
515        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
516        let trigger = OnWatermarkTrigger::new();
517        let mut manager = WindowManager::new(assigner, trigger);
518
519        // Assign events to different windows
520        manager.assign_event(create_timestamp(500)).unwrap();
521        manager.assign_event(create_timestamp(1500)).unwrap();
522        manager.assign_event(create_timestamp(2500)).unwrap();
523
524        assert_eq!(manager.active_window_count(), 3);
525
526        // Advance watermark to trigger first window
527        let ready = manager.advance_watermark(create_timestamp(1000)).unwrap();
528        assert_eq!(ready.len(), 1);
529
530        // Advance to trigger second window
531        let ready = manager.advance_watermark(create_timestamp(2000)).unwrap();
532        assert_eq!(ready.len(), 1);
533    }
534
535    #[test]
536    fn test_window_manager_sliding_windows() {
537        let assigner = SlidingWindowAssigner::new(
538            Duration::milliseconds(1000),
539            Duration::milliseconds(500),
540        );
541        let trigger = OnWatermarkTrigger::new();
542        let mut manager = WindowManager::new(assigner, trigger);
543
544        // Event at 700ms should be in 2 windows
545        let windows = manager.assign_event(create_timestamp(700)).unwrap();
546        assert_eq!(windows.len(), 2);
547        assert_eq!(manager.active_window_count(), 2);
548
549        // Both windows should have the event
550        for window in windows {
551            let metadata = manager.get_window(&window.id).unwrap();
552            assert_eq!(metadata.stats.event_count, 1);
553        }
554    }
555
556    #[test]
557    fn test_window_manager_late_events() {
558        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
559        let trigger = OnWatermarkTrigger::new();
560        let mut manager = WindowManager::new(assigner, trigger);
561
562        // Advance watermark
563        manager.advance_watermark(create_timestamp(1000)).unwrap();
564
565        // Try to process late event (within threshold)
566        let windows = manager.assign_event(create_timestamp(900)).unwrap();
567        assert_eq!(windows.len(), 1);
568
569        // Try to process very late event (beyond threshold)
570        manager.advance_watermark(create_timestamp(10000)).unwrap();
571        let windows = manager.assign_event(create_timestamp(500)).unwrap();
572        assert_eq!(windows.len(), 0); // Should be dropped
573    }
574
575    #[test]
576    fn test_window_manager_late_events_disallowed() {
577        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
578        let trigger = OnWatermarkTrigger::new();
579        let mut manager = WindowManager::with_late_events(
580            assigner,
581            trigger,
582            false, // Don't allow late events
583            Duration::minutes(5),
584        );
585
586        // Advance watermark
587        manager.advance_watermark(create_timestamp(1000)).unwrap();
588
589        // Try to process late event
590        let result = manager.assign_event(create_timestamp(900));
591        assert!(result.is_err());
592    }
593
594    #[test]
595    fn test_window_manager_cleanup() {
596        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
597        let trigger = OnWatermarkTrigger::new();
598        let mut manager = WindowManager::new(assigner, trigger);
599
600        // Create and trigger windows
601        manager.assign_event(create_timestamp(500)).unwrap();
602        manager.assign_event(create_timestamp(1500)).unwrap();
603
604        manager.advance_watermark(create_timestamp(1000)).unwrap();
605        manager.advance_watermark(create_timestamp(2000)).unwrap();
606
607        assert_eq!(manager.active_window_count(), 2);
608
609        // Cleanup old windows (both should be old enough)
610        let removed = manager.cleanup_old_windows(Duration::milliseconds(0));
611        assert_eq!(removed, 2);
612        assert_eq!(manager.active_window_count(), 0);
613    }
614
615    #[test]
616    fn test_window_manager_remove_window() {
617        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
618        let trigger = OnWatermarkTrigger::new();
619        let mut manager = WindowManager::new(assigner, trigger);
620
621        let windows = manager.assign_event(create_timestamp(500)).unwrap();
622        let window_id = &windows[0].id;
623
624        assert_eq!(manager.active_window_count(), 1);
625
626        let removed = manager.remove_window(window_id);
627        assert!(removed.is_some());
628        assert_eq!(manager.active_window_count(), 0);
629
630        // Try to remove again
631        let removed = manager.remove_window(window_id);
632        assert!(removed.is_none());
633    }
634
635    #[test]
636    fn test_window_stats() {
637        let mut stats = WindowStats::new();
638
639        assert_eq!(stats.event_count, 0);
640        assert!(stats.min_event_time.is_none());
641        assert!(stats.max_event_time.is_none());
642        assert!(!stats.is_closed());
643
644        stats.record_event(create_timestamp(500));
645        stats.record_event(create_timestamp(1000));
646        stats.record_event(create_timestamp(300));
647
648        assert_eq!(stats.event_count, 3);
649        assert_eq!(stats.min_event_time, Some(create_timestamp(300)));
650        assert_eq!(stats.max_event_time, Some(create_timestamp(1000)));
651
652        stats.close();
653        assert!(stats.is_closed());
654    }
655
656    #[test]
657    fn test_window_manager_statistics() {
658        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
659        let trigger = OnWatermarkTrigger::new();
660        let mut manager = WindowManager::new(assigner, trigger);
661
662        // Add events to multiple windows
663        manager.assign_event(create_timestamp(100)).unwrap();
664        manager.assign_event(create_timestamp(200)).unwrap();
665        manager.assign_event(create_timestamp(1500)).unwrap();
666
667        let stats = manager.get_statistics();
668        assert_eq!(stats.len(), 2);
669
670        // Check first window stats
671        let first_window_stats = stats.values()
672            .find(|s| s.event_count == 2)
673            .unwrap();
674        assert_eq!(first_window_stats.min_event_time, Some(create_timestamp(100)));
675        assert_eq!(first_window_stats.max_event_time, Some(create_timestamp(200)));
676    }
677
678    #[test]
679    fn test_window_manager_reset() {
680        let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
681        let trigger = OnWatermarkTrigger::new();
682        let mut manager = WindowManager::new(assigner, trigger);
683
684        manager.assign_event(create_timestamp(500)).unwrap();
685        manager.advance_watermark(create_timestamp(1000)).unwrap();
686
687        assert_eq!(manager.active_window_count(), 1);
688        assert!(manager.current_watermark().is_some());
689
690        manager.reset();
691
692        assert_eq!(manager.active_window_count(), 0);
693        assert!(manager.current_watermark().is_none());
694    }
695}