vector/transforms/aggregate/
transform.rs1use super::{AggregateConfig, AggregationMode};
2
3use std::{
4 collections::{HashMap, hash_map::Entry},
5 pin::Pin,
6 time::Duration,
7};
8
9use async_stream::stream;
10use futures::{Stream, StreamExt};
11use vector_lib::event::{
12 MetricValue,
13 metric::{Metric, MetricData, MetricKind, MetricSeries},
14};
15
16use crate::{
17 event::{Event, EventMetadata},
18 internal_events::{AggregateEventRecorded, AggregateFlushed, AggregateUpdateFailed},
19 transforms::TaskTransform,
20};
21
22#[derive(Clone, Debug, Default, PartialEq)]
23enum InnerMode {
24 #[default]
26 Auto,
27
28 Sum,
30
31 Latest,
33
34 Count,
36
37 Diff {
39 prev_map: HashMap<MetricSeries, MetricEntry>,
40 },
41
42 Max,
44
45 Min,
47
48 Mean {
50 multi_map: HashMap<MetricSeries, Vec<MetricEntry>>,
51 },
52
53 Stdev {
55 multi_map: HashMap<MetricSeries, Vec<MetricEntry>>,
56 },
57}
58
59impl From<AggregationMode> for InnerMode {
60 fn from(value: AggregationMode) -> Self {
61 match value {
62 AggregationMode::Auto => InnerMode::Auto,
63 AggregationMode::Sum => InnerMode::Sum,
64 AggregationMode::Latest => InnerMode::Latest,
65 AggregationMode::Count => InnerMode::Count,
66 AggregationMode::Diff => InnerMode::Diff {
67 prev_map: HashMap::default(),
68 },
69 AggregationMode::Max => InnerMode::Max,
70 AggregationMode::Min => InnerMode::Min,
71 AggregationMode::Mean => InnerMode::Mean {
72 multi_map: HashMap::default(),
73 },
74 AggregationMode::Stdev => InnerMode::Stdev {
75 multi_map: HashMap::default(),
76 },
77 }
78 }
79}
80
81type MetricEntry = (MetricData, EventMetadata);
82
83#[derive(Debug)]
84pub struct Aggregate {
85 interval: Duration,
86 map: HashMap<MetricSeries, MetricEntry>,
87 mode: InnerMode,
88}
89
90impl Aggregate {
91 pub fn new(config: &AggregateConfig) -> crate::Result<Self> {
92 Ok(Self {
93 interval: Duration::from_millis(config.interval_ms),
94 map: Default::default(),
95 mode: config.mode.into(),
96 })
97 }
98
99 pub fn record(&mut self, event: Event) -> Option<Event> {
100 let (series, data, metadata) = event.into_metric().into_parts();
101
102 match (&mut self.mode, data.kind) {
103 (InnerMode::Sum, MetricKind::Absolute)
104 | (InnerMode::Latest | InnerMode::Diff { .. }, MetricKind::Incremental)
105 | (InnerMode::Max | InnerMode::Min, MetricKind::Incremental)
106 | (InnerMode::Mean { .. } | InnerMode::Stdev { .. }, MetricKind::Incremental) => {
107 return Some(Event::Metric(Metric::from_parts(series, data, metadata)));
108 }
109 (InnerMode::Auto | InnerMode::Sum, MetricKind::Incremental) => {
110 self.record_sum(series, data, metadata);
111 }
112 (InnerMode::Auto, MetricKind::Absolute)
113 | (InnerMode::Latest | InnerMode::Diff { .. }, MetricKind::Absolute) => {
114 self.map.insert(series, (data, metadata));
115 }
116 (InnerMode::Count, _) => {
117 self.record_count(series, data, metadata);
118 }
119 (InnerMode::Max | InnerMode::Min, MetricKind::Absolute) => {
120 self.record_comparison(series, data, metadata);
121 }
122 (
123 InnerMode::Mean { multi_map } | InnerMode::Stdev { multi_map },
124 MetricKind::Absolute,
125 ) => {
126 if matches!(data.value, MetricValue::Gauge { value: _ }) {
127 match multi_map.entry(series) {
128 Entry::Occupied(mut entry) => entry.get_mut().push((data, metadata)),
129 Entry::Vacant(entry) => {
130 entry.insert(vec![(data, metadata)]);
131 }
132 }
133 }
134 }
135 }
136 emit!(AggregateEventRecorded);
137 None
138 }
139
140 fn record_count(
141 &mut self,
142 series: MetricSeries,
143 mut data: MetricData,
144 metadata: EventMetadata,
145 ) {
146 let mut count_data = data.clone();
147 let existing = self.map.entry(series).or_insert_with(|| {
148 *data.value_mut() = MetricValue::Counter { value: 0f64 };
149 (data.clone(), metadata.clone())
150 });
151 *count_data.value_mut() = MetricValue::Counter { value: 1f64 };
152 if existing.0.kind == data.kind && existing.0.update(&count_data) {
153 existing.1.merge(metadata);
154 } else {
155 emit!(AggregateUpdateFailed);
156 }
157 }
158
159 fn record_sum(&mut self, series: MetricSeries, data: MetricData, metadata: EventMetadata) {
160 match self.map.entry(series) {
161 Entry::Occupied(mut entry) => {
162 let existing = entry.get_mut();
163 if existing.0.kind == data.kind && existing.0.update(&data) {
165 existing.1.merge(metadata);
166 } else {
167 emit!(AggregateUpdateFailed);
168 *existing = (data, metadata);
169 }
170 }
171 Entry::Vacant(entry) => {
172 entry.insert((data, metadata));
173 }
174 }
175 }
176
177 fn record_comparison(
178 &mut self,
179 series: MetricSeries,
180 data: MetricData,
181 metadata: EventMetadata,
182 ) {
183 match self.map.entry(series) {
184 Entry::Occupied(mut entry) => {
185 let existing = entry.get_mut();
186 if existing.0.kind == data.kind {
188 if let MetricValue::Gauge {
189 value: existing_value,
190 } = existing.0.value()
191 && let MetricValue::Gauge { value: new_value } = data.value()
192 {
193 let should_update = match self.mode {
194 InnerMode::Max => new_value > existing_value,
195 InnerMode::Min => new_value < existing_value,
196 _ => false,
197 };
198 if should_update {
199 *existing = (data, metadata);
200 }
201 }
202 } else {
203 emit!(AggregateUpdateFailed);
204 *existing = (data, metadata);
205 }
206 }
207 Entry::Vacant(entry) => {
208 entry.insert((data, metadata));
209 }
210 }
211 }
212
213 pub fn flush_into(&mut self, output: &mut Vec<Event>) {
214 let map = std::mem::take(&mut self.map);
215 for (series, entry) in map.clone().into_iter() {
216 let mut metric = Metric::from_parts(series, entry.0, entry.1);
217 if let InnerMode::Diff { prev_map } = &self.mode
218 && let Some(prev_entry) = prev_map.get(metric.series())
219 && metric.data().kind == prev_entry.0.kind
220 && !metric.subtract(&prev_entry.0)
221 {
222 emit!(AggregateUpdateFailed);
223 }
224 output.push(Event::Metric(metric));
225 }
226
227 let multi_map = match &mut self.mode {
228 InnerMode::Mean { multi_map } | InnerMode::Stdev { multi_map } => {
229 std::mem::take(multi_map)
230 }
231 _ => HashMap::default(),
232 };
233
234 'outer: for (series, entries) in multi_map.into_iter() {
235 if entries.is_empty() {
236 continue;
237 }
238
239 let (mut final_sum, mut final_metadata) = entries.first().unwrap().clone();
240 for (data, metadata) in entries.iter().skip(1) {
241 if !final_sum.update(data) {
242 emit!(AggregateUpdateFailed);
244 continue 'outer;
245 }
246 final_metadata.merge(metadata.clone());
247 }
248
249 let final_mean_value = if let MetricValue::Gauge { value } = final_sum.value_mut() {
250 *value /= entries.len() as f64;
252 *value
253 } else {
254 0.0
255 };
256
257 let final_mean = final_sum.clone();
258 match self.mode {
259 InnerMode::Mean { .. } => {
260 let metric = Metric::from_parts(series, final_mean, final_metadata);
261 output.push(Event::Metric(metric));
262 }
263 InnerMode::Stdev { .. } => {
264 let variance = entries
265 .iter()
266 .filter_map(|(data, _)| {
267 if let MetricValue::Gauge { value } = data.value() {
268 let diff = final_mean_value - value;
269 Some(diff * diff)
270 } else {
271 None
272 }
273 })
274 .sum::<f64>()
275 / entries.len() as f64;
276 let mut final_stdev = final_mean;
277 if let MetricValue::Gauge { value } = final_stdev.value_mut() {
278 *value = variance.sqrt()
279 }
280 let metric = Metric::from_parts(series, final_stdev, final_metadata);
281 output.push(Event::Metric(metric));
282 }
283 _ => (),
284 }
285 }
286
287 if let InnerMode::Diff { prev_map } = &mut self.mode {
288 *prev_map = map;
289 }
290 emit!(AggregateFlushed);
291 }
292}
293
294impl TaskTransform<Event> for Aggregate {
295 fn transform(
296 mut self: Box<Self>,
297 mut input_rx: Pin<Box<dyn Stream<Item = Event> + Send>>,
298 ) -> Pin<Box<dyn Stream<Item = Event> + Send>>
299 where
300 Self: 'static,
301 {
302 let mut flush_stream = tokio::time::interval(self.interval);
303
304 Box::pin(stream! {
305 let mut output = Vec::new();
306 let mut done = false;
307 while !done {
308 tokio::select! {
309 _ = flush_stream.tick() => {
310 self.flush_into(&mut output);
311 },
312 maybe_event = input_rx.next() => {
313 match maybe_event {
314 None => {
315 self.flush_into(&mut output);
316 done = true;
317 }
318 Some(event) => {
319 if let Some(passthrough) = self.record(event) {
320 output.push(passthrough);
321 }
322 }
323 }
324 }
325 };
326 for event in output.drain(..) {
327 yield event;
328 }
329 }
330 })
331 }
332}