llm_optimizer_processor/aggregation/mod.rs
1//! Aggregation module for computing statistics over streaming data
2//!
3//! This module provides a comprehensive set of aggregators for computing various
4//! statistics over streaming data. All aggregators support:
5//!
6//! - **Incremental updates**: Feed values one at a time
7//! - **Batch updates**: Feed multiple values at once
8//! - **Accumulator merging**: Combine results from distributed processing
9//! - **State serialization**: Persist and restore aggregator state
10//!
11//! # Available Aggregators
12//!
13//! - [`CountAggregator`]: Count the number of values
14//! - [`SumAggregator`]: Sum all values
15//! - [`AverageAggregator`]: Compute the mean
16//! - [`MinAggregator`]: Track the minimum value
17//! - [`MaxAggregator`]: Track the maximum value
18//! - [`PercentileAggregator`]: Compute percentiles (p50, p95, p99, etc.)
19//! - [`StandardDeviationAggregator`]: Compute standard deviation
20//! - [`CompositeAggregator`]: Run multiple aggregations in parallel
21//!
22//! # Examples
23//!
24//! ## Basic Usage
25//!
26//! ```rust
27//! use processor::aggregation::{Aggregator, AverageAggregator};
28//!
29//! let mut agg = AverageAggregator::new();
30//! agg.update(10.0).unwrap();
31//! agg.update(20.0).unwrap();
32//! agg.update(30.0).unwrap();
33//!
34//! assert_eq!(agg.finalize().unwrap(), 20.0);
35//! ```
36//!
37//! ## Distributed Processing
38//!
39//! ```rust
40//! use processor::aggregation::{Aggregator, SumAggregator};
41//!
42//! // Process data in parallel
43//! let mut agg1 = SumAggregator::new();
44//! agg1.update_batch(&[1.0, 2.0, 3.0]).unwrap();
45//!
46//! let mut agg2 = SumAggregator::new();
47//! agg2.update_batch(&[4.0, 5.0, 6.0]).unwrap();
48//!
49//! // Merge results
50//! let acc2 = agg2.accumulator();
51//! agg1.merge(acc2).unwrap();
52//!
53//! assert_eq!(agg1.finalize().unwrap(), 21.0);
54//! ```
55//!
56//! ## Composite Aggregation
57//!
58//! ```rust
59//! use processor::aggregation::CompositeAggregator;
60//!
61//! let mut composite = CompositeAggregator::new();
62//! composite
63//! .add_count("request_count")
64//! .add_avg("avg_latency")
65//! .add_p95("p95_latency")
66//! .add_stddev("stddev_latency");
67//!
68//! // Feed data
69//! for latency in vec![100.0, 150.0, 200.0, 250.0, 300.0] {
70//! composite.update(latency).unwrap();
71//! }
72//!
73//! // Get all results at once
74//! let results = composite.finalize().unwrap();
75//! println!("Count: {}", results.get_count("request_count").unwrap());
76//! println!("Avg: {:.2}", results.get_value("avg_latency").unwrap());
77//! println!("P95: {:.2}", results.get_value("p95_latency").unwrap());
78//! ```
79
80// Re-name to avoid collision with trait
81mod trait_;
82
83mod count;
84mod sum;
85mod avg;
86mod minmax;
87mod percentile;
88mod stddev;
89mod composite;
90pub mod statistics;
91
92// Re-export the trait
93pub use trait_::{Aggregator, ToF64};
94
95// Re-export all aggregators
96pub use count::{CountAggregator, CountAccumulator};
97pub use sum::{SumAggregator, SumAccumulator};
98pub use avg::{AverageAggregator, AverageAccumulator};
99pub use minmax::{MinAggregator, MaxAggregator, MinAccumulator, MaxAccumulator};
100pub use percentile::{PercentileAggregator, PercentileAccumulator};
101pub use stddev::{StandardDeviationAggregator, StdDevAccumulator};
102pub use composite::{CompositeAggregator, AggregationResult};
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_multiple_aggregators_together() {
110 let data = vec![10.0, 20.0, 30.0, 40.0, 50.0];
111
112 let mut count = CountAggregator::new();
113 let mut sum = SumAggregator::new();
114 let mut avg = AverageAggregator::new();
115 let mut min = MinAggregator::new();
116 let mut max = MaxAggregator::new();
117
118 for value in &data {
119 count.update(*value).unwrap();
120 sum.update(*value).unwrap();
121 avg.update(*value).unwrap();
122 min.update(*value).unwrap();
123 max.update(*value).unwrap();
124 }
125
126 assert_eq!(count.finalize().unwrap(), 5);
127 assert_eq!(sum.finalize().unwrap(), 150.0);
128 assert_eq!(avg.finalize().unwrap(), 30.0);
129 assert_eq!(min.finalize().unwrap(), 10.0);
130 assert_eq!(max.finalize().unwrap(), 50.0);
131 }
132
133 #[test]
134 fn test_serialization_roundtrip() {
135 let mut avg = AverageAggregator::<f64>::new();
136 avg.update_batch(&[10.0, 20.0, 30.0]).unwrap();
137
138 // Serialize accumulator
139 let acc = avg.accumulator();
140 let serialized = serde_json::to_string(&acc).unwrap();
141
142 // Deserialize and merge into new aggregator
143 let deserialized: AverageAccumulator = serde_json::from_str(&serialized).unwrap();
144 let mut new_avg = AverageAggregator::<f64>::new();
145 new_avg.merge(deserialized).unwrap();
146
147 assert_eq!(new_avg.finalize().unwrap(), 20.0);
148 }
149
150 #[test]
151 fn test_distributed_processing_scenario() {
152 // Simulate processing data across 3 workers
153 let worker1_data = vec![1.0, 2.0, 3.0];
154 let worker2_data = vec![4.0, 5.0, 6.0];
155 let worker3_data = vec![7.0, 8.0, 9.0];
156
157 // Each worker computes its own aggregation
158 let mut agg1 = AverageAggregator::new();
159 agg1.update_batch(&worker1_data).unwrap();
160
161 let mut agg2 = AverageAggregator::new();
162 agg2.update_batch(&worker2_data).unwrap();
163
164 let mut agg3 = AverageAggregator::new();
165 agg3.update_batch(&worker3_data).unwrap();
166
167 // Merge all results
168 let mut combined = AverageAggregator::<f64>::new();
169 combined.merge(agg1.accumulator()).unwrap();
170 combined.merge(agg2.accumulator()).unwrap();
171 combined.merge(agg3.accumulator()).unwrap();
172
173 // Result should be average of 1-9
174 assert_eq!(combined.finalize().unwrap(), 5.0);
175 assert_eq!(combined.count(), 9);
176 }
177}