llm_optimizer_processor/window/
mod.rs1pub mod types;
109pub mod assigner;
110pub mod trigger;
111pub mod manager;
112
113pub use types::{Window, WindowBounds, WindowType};
114pub use assigner::{
115 WindowAssigner,
116 TumblingWindowAssigner,
117 SlidingWindowAssigner,
118 SessionWindowAssigner,
119};
120pub use trigger::{
121 WindowTrigger,
122 TriggerResult,
123 TriggerContext,
124 OnWatermarkTrigger,
125 ProcessingTimeTrigger,
126 CountTrigger,
127 CompositeTrigger,
128 CompositeMode,
129 ImmediateTrigger,
130 NeverTrigger,
131};
132pub use manager::{WindowManager, WindowMetadata, WindowStats};
133
134use chrono::DateTime;
135use std::collections::HashMap;
136
137#[derive(Debug)]
141pub struct WindowMerger {
142 windows: HashMap<String, Window>,
144 session_gap: chrono::Duration,
146}
147
148impl WindowMerger {
149 pub fn new(session_gap: chrono::Duration) -> Self {
151 Self {
152 windows: HashMap::new(),
153 session_gap,
154 }
155 }
156
157 pub fn add_and_merge(&mut self, window: Window) -> (Window, Vec<Window>) {
161 let mut merged_windows = Vec::new();
162 let mut result_window = window.clone();
163
164 let mut to_remove = Vec::new();
166 for (id, existing_window) in &self.windows {
167 if result_window.can_merge(existing_window, self.session_gap) {
168 result_window = result_window.merge(existing_window);
169 merged_windows.push(existing_window.clone());
170 to_remove.push(id.clone());
171 }
172 }
173
174 for id in to_remove {
176 self.windows.remove(&id);
177 }
178
179 self.windows.insert(result_window.id.clone(), result_window.clone());
181
182 (result_window, merged_windows)
183 }
184
185 pub fn windows(&self) -> Vec<&Window> {
187 self.windows.values().collect()
188 }
189
190 pub fn remove(&mut self, window_id: &str) -> Option<Window> {
192 self.windows.remove(window_id)
193 }
194
195 pub fn remove_old_windows(&mut self, watermark: DateTime<chrono::Utc>) -> Vec<Window> {
197 let mut removed = Vec::new();
198 self.windows.retain(|_, window| {
199 if window.bounds.end <= watermark {
200 removed.push(window.clone());
201 false
202 } else {
203 true
204 }
205 });
206 removed
207 }
208
209 pub fn clear(&mut self) {
211 self.windows.clear();
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use chrono::{Duration, TimeZone, Utc};
219
220 fn create_timestamp(millis: i64) -> DateTime<Utc> {
221 Utc.timestamp_millis_opt(millis).unwrap()
222 }
223
224 fn create_window(start_millis: i64, end_millis: i64) -> Window {
225 Window::new(WindowBounds::new(
226 create_timestamp(start_millis),
227 create_timestamp(end_millis),
228 ))
229 }
230
231 #[test]
232 fn test_window_merger_no_overlap() {
233 let mut merger = WindowMerger::new(Duration::milliseconds(1000));
234
235 let window1 = create_window(0, 1000);
237 let (result, merged) = merger.add_and_merge(window1.clone());
238 assert_eq!(result, window1);
239 assert_eq!(merged.len(), 0);
240
241 let window2 = create_window(3000, 4000);
243 let (result, merged) = merger.add_and_merge(window2.clone());
244 assert_eq!(result, window2);
245 assert_eq!(merged.len(), 0);
246
247 assert_eq!(merger.windows().len(), 2);
249 }
250
251 #[test]
252 fn test_window_merger_with_overlap() {
253 let mut merger = WindowMerger::new(Duration::milliseconds(1000));
254
255 let window1 = create_window(0, 1000);
257 merger.add_and_merge(window1);
258
259 let window2 = create_window(500, 1500);
261 let (result, merged) = merger.add_and_merge(window2);
262
263 assert_eq!(result.bounds.start, create_timestamp(0));
265 assert_eq!(result.bounds.end, create_timestamp(1500));
266 assert_eq!(merged.len(), 1);
267
268 assert_eq!(merger.windows().len(), 1);
270 }
271
272 #[test]
273 fn test_window_merger_within_gap() {
274 let mut merger = WindowMerger::new(Duration::milliseconds(1000));
275
276 let window1 = create_window(0, 1000);
278 merger.add_and_merge(window1);
279
280 let window2 = create_window(1500, 2500);
282 let (result, merged) = merger.add_and_merge(window2);
283
284 assert_eq!(result.bounds.start, create_timestamp(0));
286 assert_eq!(result.bounds.end, create_timestamp(2500));
287 assert_eq!(merged.len(), 1);
288 }
289
290 #[test]
291 fn test_window_merger_multiple_merges() {
292 let mut merger = WindowMerger::new(Duration::milliseconds(500));
293
294 merger.add_and_merge(create_window(0, 1000));
296 merger.add_and_merge(create_window(2000, 3000));
297 merger.add_and_merge(create_window(5000, 6000)); assert_eq!(merger.windows().len(), 3);
301
302 let window = create_window(1000, 2000);
304 let (result, merged) = merger.add_and_merge(window);
305
306 assert_eq!(result.bounds.start, create_timestamp(0));
308 assert_eq!(result.bounds.end, create_timestamp(3000));
309 assert_eq!(merged.len(), 2);
310
311 assert_eq!(merger.windows().len(), 2);
313 }
314
315 #[test]
316 fn test_window_merger_remove_old() {
317 let mut merger = WindowMerger::new(Duration::milliseconds(500));
318
319 merger.add_and_merge(create_window(0, 1000));
321 merger.add_and_merge(create_window(2000, 3000));
322 merger.add_and_merge(create_window(4000, 5000));
323
324 let removed = merger.remove_old_windows(create_timestamp(3000));
326 assert_eq!(removed.len(), 2); assert_eq!(merger.windows().len(), 1); }
329
330 #[test]
331 fn test_tumbling_window_complete_flow() {
332 use assigner::TumblingWindowAssigner;
333 use trigger::OnWatermarkTrigger;
334
335 let assigner = TumblingWindowAssigner::new(Duration::milliseconds(1000));
336 let mut trigger = OnWatermarkTrigger::new();
337
338 let windows = assigner.assign_windows(create_timestamp(500));
340 assert_eq!(windows.len(), 1);
341 assert_eq!(windows[0].bounds.start, create_timestamp(0));
342 assert_eq!(windows[0].bounds.end, create_timestamp(1000));
343
344 let ctx = TriggerContext {
346 window: windows[0].clone(),
347 event_time: Some(create_timestamp(500)),
348 processing_time: create_timestamp(1500),
349 watermark: Some(create_timestamp(900)),
350 element_count: 1,
351 };
352 assert_eq!(trigger.on_watermark(&ctx), TriggerResult::Continue);
353
354 let ctx = TriggerContext {
356 window: windows[0].clone(),
357 event_time: Some(create_timestamp(500)),
358 processing_time: create_timestamp(1500),
359 watermark: Some(create_timestamp(1000)),
360 element_count: 1,
361 };
362 assert_eq!(trigger.on_watermark(&ctx), TriggerResult::Fire);
363 }
364
365 #[test]
366 fn test_sliding_window_complete_flow() {
367 use assigner::SlidingWindowAssigner;
368
369 let assigner = SlidingWindowAssigner::new(
370 Duration::milliseconds(1000),
371 Duration::milliseconds(500),
372 );
373
374 let windows = assigner.assign_windows(create_timestamp(700));
376 assert_eq!(windows.len(), 2);
377
378 assert_eq!(windows[0].bounds.start, create_timestamp(0));
380 assert_eq!(windows[0].bounds.end, create_timestamp(1000));
381 assert_eq!(windows[1].bounds.start, create_timestamp(500));
382 assert_eq!(windows[1].bounds.end, create_timestamp(1500));
383 }
384
385 #[test]
386 fn test_session_window_complete_flow() {
387 use assigner::SessionWindowAssigner;
388
389 let assigner = SessionWindowAssigner::new(Duration::milliseconds(1000));
390 let mut merger = WindowMerger::new(Duration::milliseconds(1000));
391
392 let windows = assigner.assign_windows(create_timestamp(0));
394 let (_window1, _) = merger.add_and_merge(windows[0].clone());
395
396 let windows = assigner.assign_windows(create_timestamp(500));
398 let (window2, merged) = merger.add_and_merge(windows[0].clone());
399
400 assert_eq!(merged.len(), 1);
402 assert_eq!(window2.bounds.start, create_timestamp(0));
403 assert_eq!(window2.bounds.end, create_timestamp(1500));
404
405 let windows = assigner.assign_windows(create_timestamp(3000));
407 let (window3, merged) = merger.add_and_merge(windows[0].clone());
408
409 assert_eq!(merged.len(), 0);
411 assert_eq!(window3.bounds.start, create_timestamp(3000));
412 assert_eq!(window3.bounds.end, create_timestamp(4000));
413 }
414}