llm_optimizer_processor/window/
manager.rs1use 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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47pub struct WindowStats {
48 pub event_count: u64,
50 pub created_at: DateTime<Utc>,
52 pub last_update: DateTime<Utc>,
54 pub closed_at: Option<DateTime<Utc>>,
56 pub min_event_time: Option<DateTime<Utc>>,
58 pub max_event_time: Option<DateTime<Utc>>,
60}
61
62impl WindowStats {
63 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 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 pub fn close(&mut self) {
96 self.closed_at = Some(Utc::now());
97 }
98
99 pub fn is_closed(&self) -> bool {
101 self.closed_at.is_some()
102 }
103}
104
105#[derive(Debug, Clone)]
107pub struct WindowMetadata {
108 pub window: Window,
110 pub stats: WindowStats,
112 pub triggered: bool,
114}
115
116impl WindowMetadata {
117 pub fn new(window: Window) -> Self {
119 Self {
120 window,
121 stats: WindowStats::new(),
122 triggered: false,
123 }
124 }
125
126 pub fn record_event(&mut self, event_time: DateTime<Utc>) {
128 self.stats.record_event(event_time);
129 }
130
131 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 pub fn close(&mut self) {
164 self.stats.close();
165 }
166}
167
168pub struct WindowManager<A, T>
177where
178 A: WindowAssigner,
179 T: WindowTrigger,
180{
181 assigner: A,
183 trigger: T,
185 active_windows: Arc<DashMap<String, WindowMetadata>>,
187 windows_by_end_time: BTreeMap<i64, Vec<String>>,
189 current_watermark: Option<DateTime<Utc>>,
191 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 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 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 pub fn assign_event(
236 &mut self,
237 event_time: DateTime<Utc>,
238 ) -> Result<Vec<Window>, ProcessorError> {
239 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 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 for window in &windows {
282 let mut metadata = self.active_windows
283 .entry(window.id.clone())
284 .or_insert_with(|| {
285 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 pub fn advance_watermark(
312 &mut self,
313 watermark: DateTime<Utc>,
314 ) -> Result<Vec<String>, ProcessorError> {
315 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 let mut ready_windows = Vec::new();
337 let watermark_millis = watermark.timestamp_millis();
338
339 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 pub fn get_window(&self, window_id: &str) -> Option<WindowMetadata> {
368 self.active_windows.get(window_id).map(|m| m.clone())
369 }
370
371 pub fn remove_window(&mut self, window_id: &str) -> Option<WindowMetadata> {
373 let metadata = self.active_windows.remove(window_id).map(|(_, m)| m)?;
375
376 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 pub fn active_window_ids(&self) -> Vec<String> {
395 self.active_windows.iter().map(|e| e.key().clone()).collect()
396 }
397
398 pub fn active_window_count(&self) -> usize {
400 self.active_windows.len()
401 }
402
403 pub fn current_watermark(&self) -> Option<DateTime<Utc>> {
405 self.current_watermark
406 }
407
408 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 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 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 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 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 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 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 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 let ready = manager.advance_watermark(create_timestamp(1000)).unwrap();
504 assert_eq!(ready.len(), 1);
505
506 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 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 let ready = manager.advance_watermark(create_timestamp(1000)).unwrap();
528 assert_eq!(ready.len(), 1);
529
530 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 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 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 manager.advance_watermark(create_timestamp(1000)).unwrap();
564
565 let windows = manager.assign_event(create_timestamp(900)).unwrap();
567 assert_eq!(windows.len(), 1);
568
569 manager.advance_watermark(create_timestamp(10000)).unwrap();
571 let windows = manager.assign_event(create_timestamp(500)).unwrap();
572 assert_eq!(windows.len(), 0); }
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, Duration::minutes(5),
584 );
585
586 manager.advance_watermark(create_timestamp(1000)).unwrap();
588
589 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 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 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 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 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 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}