1use std::{
2 marker::PhantomData,
3 time::{Duration, Instant},
4};
5
6use indexmap::IndexMap;
7use lru::LruCache;
8use serde_with::serde_as;
9use snafu::Snafu;
10use vector_config_macros::configurable_component;
11use vector_lib::{
12 ByteSizeOf,
13 event::{
14 EventMetadata, Metric, MetricKind,
15 metric::{MetricData, MetricSeries},
16 },
17};
18
19#[derive(Debug, Snafu, PartialEq, Eq)]
20pub enum NormalizerError {
21 #[snafu(display("`max_bytes` must be greater than zero"))]
22 InvalidMaxBytes,
23 #[snafu(display("`max_events` must be greater than zero"))]
24 InvalidMaxEvents,
25 #[snafu(display("`time_to_live` must be greater than zero"))]
26 InvalidTimeToLive,
27}
28
29#[serde_as]
31#[configurable_component]
32#[derive(Clone, Copy, Debug, Default)]
33pub struct NormalizerConfig<D: NormalizerSettings + Clone> {
34 #[serde(default = "default_max_bytes::<D>")]
36 #[configurable(metadata(docs::type_unit = "bytes"))]
37 pub max_bytes: Option<usize>,
38
39 #[serde(default = "default_max_events::<D>")]
41 #[configurable(metadata(docs::type_unit = "events"))]
42 pub max_events: Option<usize>,
43
44 #[serde(default = "default_time_to_live::<D>")]
46 #[configurable(metadata(docs::type_unit = "seconds"))]
47 #[configurable(metadata(docs::human_name = "Time To Live"))]
48 pub time_to_live: Option<u64>,
49
50 #[serde(skip)]
51 pub _d: PhantomData<D>,
52}
53
54const fn default_max_bytes<D: NormalizerSettings>() -> Option<usize> {
55 D::MAX_BYTES
56}
57
58const fn default_max_events<D: NormalizerSettings>() -> Option<usize> {
59 D::MAX_EVENTS
60}
61
62const fn default_time_to_live<D: NormalizerSettings>() -> Option<u64> {
63 D::TIME_TO_LIVE
64}
65
66impl<D: NormalizerSettings + Clone> NormalizerConfig<D> {
67 pub fn validate(&self) -> Result<NormalizerConfig<D>, NormalizerError> {
68 let config = NormalizerConfig::<D> {
69 max_bytes: self.max_bytes.or(D::MAX_BYTES),
70 max_events: self.max_events.or(D::MAX_EVENTS),
71 time_to_live: self.time_to_live.or(D::TIME_TO_LIVE),
72 _d: Default::default(),
73 };
74 match (config.max_bytes, config.max_events, config.time_to_live) {
75 (Some(0), _, _) => Err(NormalizerError::InvalidMaxBytes),
76 (_, Some(0), _) => Err(NormalizerError::InvalidMaxEvents),
77 (_, _, Some(0)) => Err(NormalizerError::InvalidTimeToLive),
78 _ => Ok(config),
79 }
80 }
81
82 pub const fn into_settings(self) -> MetricSetSettings {
83 MetricSetSettings {
84 max_bytes: self.max_bytes,
85 max_events: self.max_events,
86 time_to_live: self.time_to_live,
87 }
88 }
89}
90
91pub trait NormalizerSettings {
92 const MAX_EVENTS: Option<usize>;
93 const MAX_BYTES: Option<usize>;
94 const TIME_TO_LIVE: Option<u64>;
95}
96
97#[derive(Clone, Copy, Debug, Default)]
98pub struct DefaultNormalizerSettings;
99
100impl NormalizerSettings for DefaultNormalizerSettings {
101 const MAX_EVENTS: Option<usize> = None;
102 const MAX_BYTES: Option<usize> = None;
103 const TIME_TO_LIVE: Option<u64> = None;
104}
105
106pub trait MetricNormalize {
119 fn normalize(&mut self, state: &mut MetricSet, metric: Metric) -> Option<Metric>;
134}
135
136pub struct MetricNormalizer<N> {
142 state: MetricSet,
143 normalizer: N,
144}
145
146impl<N> MetricNormalizer<N> {
147 pub fn with_config<D: NormalizerSettings + Clone>(
149 normalizer: N,
150 config: NormalizerConfig<D>,
151 ) -> Self {
152 let settings = config
153 .validate()
154 .unwrap_or_else(|e| panic!("Invalid cache settings: {e:?}"))
155 .into_settings();
156 Self {
157 state: MetricSet::new(settings),
158 normalizer,
159 }
160 }
161
162 pub fn with_ttl(normalizer: N, ttl: Duration) -> Self {
164 Self {
165 state: MetricSet::with_policies(None, Some(TtlPolicy::new(ttl))),
166 normalizer,
167 }
168 }
169
170 pub const fn get_state_mut(&mut self) -> &mut MetricSet {
172 &mut self.state
173 }
174}
175
176impl<N: MetricNormalize> MetricNormalizer<N> {
177 pub fn normalize(&mut self, metric: Metric) -> Option<Metric> {
181 self.normalizer.normalize(&mut self.state, metric)
182 }
183}
184
185impl<N: Default> Default for MetricNormalizer<N> {
186 fn default() -> Self {
187 Self {
188 state: MetricSet::default(),
189 normalizer: N::default(),
190 }
191 }
192}
193
194impl<N> From<N> for MetricNormalizer<N> {
195 fn from(normalizer: N) -> Self {
196 Self {
197 state: MetricSet::default(),
198 normalizer,
199 }
200 }
201}
202
203#[derive(Clone, Debug)]
205pub struct MetricEntry {
206 pub data: MetricData,
208 pub metadata: EventMetadata,
210 pub timestamp: Option<Instant>,
212}
213
214impl ByteSizeOf for MetricEntry {
215 fn allocated_bytes(&self) -> usize {
216 self.data.allocated_bytes() + self.metadata.allocated_bytes()
217 }
218}
219
220impl MetricEntry {
221 pub const fn new(
223 data: MetricData,
224 metadata: EventMetadata,
225 timestamp: Option<Instant>,
226 ) -> Self {
227 Self {
228 data,
229 metadata,
230 timestamp,
231 }
232 }
233
234 pub fn from_metric(metric: Metric, timestamp: Option<Instant>) -> (MetricSeries, Self) {
236 let (series, data, metadata) = metric.into_parts();
237 let entry = Self::new(data, metadata, timestamp);
238 (series, entry)
239 }
240
241 pub fn into_metric(self, series: MetricSeries) -> Metric {
243 Metric::from_parts(series, self.data, self.metadata)
244 }
245
246 pub const fn update_timestamp(&mut self, timestamp: Option<Instant>) {
248 self.timestamp = timestamp;
249 }
250
251 pub fn is_expired(&self, ttl: Duration, reference_time: Instant) -> bool {
255 match self.timestamp {
256 Some(ts) => reference_time.duration_since(ts) >= ttl,
257 None => false,
258 }
259 }
260}
261
262#[derive(Clone, Debug)]
264pub struct CapacityPolicy {
265 pub max_bytes: Option<usize>,
267 pub max_events: Option<usize>,
269 current_memory: usize,
271}
272
273impl CapacityPolicy {
274 pub const fn new(max_bytes: Option<usize>, max_events: Option<usize>) -> Self {
276 Self {
277 max_bytes,
278 max_events,
279 current_memory: 0,
280 }
281 }
282
283 pub const fn current_memory(&self) -> usize {
285 self.current_memory
286 }
287
288 const fn remove_memory(&mut self, bytes: usize) {
290 self.current_memory = self.current_memory.saturating_sub(bytes);
291 }
292
293 pub fn free_item(&mut self, series: &MetricSeries, entry: &MetricEntry) {
296 if self.max_bytes.is_some() {
297 let freed_memory = self.item_size(series, entry);
298 self.remove_memory(freed_memory);
299 }
300 }
301
302 const fn replace_memory(&mut self, old_bytes: usize, new_bytes: usize) {
304 self.current_memory = self
305 .current_memory
306 .saturating_sub(old_bytes)
307 .saturating_add(new_bytes);
308 }
309
310 const fn exceeds_memory_limit(&self) -> bool {
312 if let Some(max_bytes) = self.max_bytes {
313 self.current_memory > max_bytes
314 } else {
315 false
316 }
317 }
318
319 const fn exceeds_entry_limit(&self, entry_count: usize) -> bool {
321 if let Some(max_events) = self.max_events {
322 entry_count > max_events
323 } else {
324 false
325 }
326 }
327
328 const fn needs_eviction(&self, entry_count: usize) -> bool {
330 self.exceeds_memory_limit() || self.exceeds_entry_limit(entry_count)
331 }
332
333 pub fn item_size(&self, series: &MetricSeries, entry: &MetricEntry) -> usize {
335 entry.allocated_bytes() + series.allocated_bytes()
336 }
337}
338
339#[derive(Clone, Debug)]
340pub struct TtlPolicy {
341 pub ttl: Duration,
343 pub cleanup_interval: Duration,
345 pub(crate) last_cleanup: Instant,
347}
348
349impl TtlPolicy {
351 pub fn new(ttl: Duration) -> Self {
354 Self {
355 ttl,
356 cleanup_interval: ttl.div_f32(10.0).max(Duration::from_secs(10)),
357 last_cleanup: Instant::now(),
358 }
359 }
360
361 pub fn should_cleanup(&self) -> Option<Instant> {
365 let now = Instant::now();
366 if now.duration_since(self.last_cleanup) >= self.cleanup_interval {
367 Some(now)
368 } else {
369 None
370 }
371 }
372
373 pub const fn mark_cleanup_done(&mut self, now: Instant) {
375 self.last_cleanup = now;
376 }
377}
378
379#[derive(Debug, Clone, Copy, Default)]
380pub struct MetricSetSettings {
381 pub max_bytes: Option<usize>,
382 pub max_events: Option<usize>,
383 pub time_to_live: Option<u64>,
384}
385
386#[derive(Clone, Debug)]
393enum MetricSetInner {
394 Unbounded(IndexMap<MetricSeries, MetricEntry>),
396 Bounded(LruCache<MetricSeries, MetricEntry>),
398}
399
400impl MetricSetInner {
401 fn len(&self) -> usize {
402 match self {
403 Self::Unbounded(m) => m.len(),
404 Self::Bounded(m) => m.len(),
405 }
406 }
407
408 fn is_empty(&self) -> bool {
409 match self {
410 Self::Unbounded(m) => m.is_empty(),
411 Self::Bounded(m) => m.is_empty(),
412 }
413 }
414
415 fn get_mut(&mut self, key: &MetricSeries) -> Option<&mut MetricEntry> {
420 match self {
421 Self::Unbounded(m) => m.get_mut(key),
422 Self::Bounded(m) => m.get_mut(key),
423 }
424 }
425
426 fn put(&mut self, key: MetricSeries, value: MetricEntry) -> Option<MetricEntry> {
428 match self {
429 Self::Unbounded(m) => m.insert(key, value),
430 Self::Bounded(m) => m.put(key, value),
431 }
432 }
433
434 fn pop(&mut self, key: &MetricSeries) -> Option<MetricEntry> {
436 match self {
437 Self::Unbounded(m) => m.swap_remove(key),
439 Self::Bounded(m) => m.pop(key),
440 }
441 }
442
443 fn iter(&self) -> MetricSetIter<'_> {
444 match self {
445 Self::Unbounded(m) => MetricSetIter::Unbounded(m.iter()),
446 Self::Bounded(m) => MetricSetIter::Bounded(m.iter()),
447 }
448 }
449}
450
451enum MetricSetIter<'a> {
452 Unbounded(indexmap::map::Iter<'a, MetricSeries, MetricEntry>),
453 Bounded(lru::Iter<'a, MetricSeries, MetricEntry>),
454}
455
456impl<'a> Iterator for MetricSetIter<'a> {
457 type Item = (&'a MetricSeries, &'a MetricEntry);
458
459 fn next(&mut self) -> Option<Self::Item> {
460 match self {
461 Self::Unbounded(it) => it.next(),
462 Self::Bounded(it) => it.next(),
463 }
464 }
465}
466
467#[derive(Clone, Debug)]
474pub struct MetricSet {
475 inner: MetricSetInner,
476 capacity_policy: Option<CapacityPolicy>,
478 ttl_policy: Option<TtlPolicy>,
480}
481
482impl MetricSet {
483 pub fn new(settings: MetricSetSettings) -> Self {
485 let capacity_policy = match (settings.max_bytes, settings.max_events) {
487 (None, None) => None,
488 (max_bytes, max_events) => Some(CapacityPolicy::new(max_bytes, max_events)),
489 };
490
491 let ttl_policy = settings
493 .time_to_live
494 .map(|ttl| TtlPolicy::new(Duration::from_secs(ttl)));
495
496 Self::with_policies(capacity_policy, ttl_policy)
497 }
498
499 pub fn with_policies(
501 capacity_policy: Option<CapacityPolicy>,
502 ttl_policy: Option<TtlPolicy>,
503 ) -> Self {
504 let inner = if capacity_policy.is_some() {
507 MetricSetInner::Bounded(LruCache::unbounded())
508 } else {
509 MetricSetInner::Unbounded(IndexMap::default())
510 };
511 Self {
512 inner,
513 capacity_policy,
514 ttl_policy,
515 }
516 }
517
518 pub const fn capacity_policy(&self) -> Option<&CapacityPolicy> {
520 self.capacity_policy.as_ref()
521 }
522
523 pub const fn ttl_policy(&self) -> Option<&TtlPolicy> {
525 self.ttl_policy.as_ref()
526 }
527
528 pub const fn ttl_policy_mut(&mut self) -> Option<&mut TtlPolicy> {
530 self.ttl_policy.as_mut()
531 }
532
533 pub fn len(&self) -> usize {
535 self.inner.len()
536 }
537
538 pub fn is_empty(&self) -> bool {
540 self.inner.is_empty()
541 }
542
543 pub fn weighted_size(&self) -> u64 {
545 self.capacity_policy
546 .as_ref()
547 .map_or(0, |cp| cp.current_memory() as u64)
548 }
549
550 fn create_timestamp(&self) -> Option<Instant> {
552 self.ttl_policy.as_ref().map(|_| Instant::now())
553 }
554
555 fn enforce_capacity_policy(&mut self) {
557 let Some(ref mut capacity_policy) = self.capacity_policy else {
558 return; };
560
561 let MetricSetInner::Bounded(ref mut lru) = self.inner else {
563 debug_assert!(false, "capacity policy set but inner is not Bounded");
564 return;
565 };
566
567 while capacity_policy.needs_eviction(lru.len()) {
569 if let Some((series, entry)) = lru.pop_lru() {
570 capacity_policy.free_item(&series, &entry);
571 } else {
572 break; }
574 }
575 }
576
577 fn maybe_cleanup(&mut self) {
579 let now = match self.ttl_policy().and_then(|config| config.should_cleanup()) {
581 Some(timestamp) => timestamp,
582 None => return, };
584
585 self.cleanup_expired(now);
587
588 if let Some(config) = self.ttl_policy_mut() {
590 config.mark_cleanup_done(now);
591 }
592 }
593
594 fn cleanup_expired(&mut self, now: Instant) {
596 let Some(ttl) = self.ttl_policy().map(|policy| policy.ttl) else {
598 return; };
600
601 let expired_keys: Vec<MetricSeries> = self
603 .inner
604 .iter()
605 .filter(|(_, e)| e.is_expired(ttl, now))
606 .map(|(s, _)| s.clone())
607 .collect();
608
609 for series in expired_keys {
611 if let Some(entry) = self.inner.pop(&series)
612 && let Some(ref mut capacity_policy) = self.capacity_policy
613 {
614 capacity_policy.free_item(&series, &entry);
615 }
616 }
617 }
618
619 fn insert_with_tracking(&mut self, series: MetricSeries, entry: MetricEntry) {
621 let Some(ref mut capacity_policy) = self.capacity_policy else {
622 self.inner.put(series, entry);
623 return; };
625
626 if capacity_policy.max_bytes.is_some() {
628 let entry_size = capacity_policy.item_size(&series, &entry);
630
631 if let Some(existing_entry) = self.inner.put(series.clone(), entry) {
632 let existing_size = capacity_policy.item_size(&series, &existing_entry);
634 capacity_policy.replace_memory(existing_size, entry_size);
635 } else {
636 capacity_policy.replace_memory(0, entry_size);
638 }
639 } else {
640 self.inner.put(series, entry);
642 }
643
644 self.enforce_capacity_policy();
646 }
647
648 pub fn into_metrics(mut self) -> Vec<Metric> {
650 self.cleanup_expired(Instant::now());
652 match self.inner {
653 MetricSetInner::Unbounded(m) => m
654 .into_iter()
655 .map(|(series, entry)| entry.into_metric(series))
656 .collect(),
657 MetricSetInner::Bounded(mut m) => {
658 let mut metrics = Vec::with_capacity(m.len());
659 while let Some((series, entry)) = m.pop_lru() {
660 metrics.push(entry.into_metric(series));
661 }
662 metrics
663 }
664 }
665 }
666
667 pub fn make_absolute(&mut self, metric: Metric) -> Option<Metric> {
670 self.maybe_cleanup();
671 match metric.kind() {
672 MetricKind::Absolute => Some(metric),
673 MetricKind::Incremental => Some(self.incremental_to_absolute(metric)),
674 }
675 }
676
677 pub fn make_incremental(&mut self, metric: Metric) -> Option<Metric> {
680 self.maybe_cleanup();
681 match metric.kind() {
682 MetricKind::Absolute => self.absolute_to_incremental(metric),
683 MetricKind::Incremental => Some(metric),
684 }
685 }
686
687 fn incremental_to_absolute(&mut self, mut metric: Metric) -> Metric {
691 let timestamp = self.create_timestamp();
692 if let Some(existing) = self.inner.get_mut(metric.series()) {
694 let mut new_value = existing.data.value().clone();
695 if new_value.add(metric.value()) {
696 metric = metric.with_value(new_value);
698 }
699 }
700 let mut cache_metric = metric.clone();
704 cache_metric.metadata_mut().take_finalizers();
705 self.insert(cache_metric, timestamp);
706 metric.into_absolute()
707 }
708
709 fn absolute_to_incremental(&mut self, mut metric: Metric) -> Option<Metric> {
712 let timestamp = self.create_timestamp();
732 if let Some(reference) = self.inner.get_mut(metric.series()) {
734 let new_value = metric.value().clone();
735 let mut new_reference = reference.clone();
738 if metric.subtract(&reference.data) {
740 new_reference.data.value = new_value;
741 new_reference.timestamp = timestamp;
742 self.insert_with_tracking(metric.series().clone(), new_reference);
743 return Some(metric.into_incremental());
744 }
745 }
747 metric.metadata_mut().take_finalizers();
750 self.insert(metric, timestamp);
751 None
752 }
753
754 fn insert(&mut self, metric: Metric, timestamp: Option<Instant>) {
755 let (series, entry) = MetricEntry::from_metric(metric, timestamp);
756 self.insert_with_tracking(series, entry);
757 }
758
759 pub fn insert_update(&mut self, metric: Metric) {
760 self.maybe_cleanup();
761 let timestamp = self.create_timestamp();
762 let update = match metric.kind() {
763 MetricKind::Absolute => Some(metric),
764 MetricKind::Incremental => {
765 match self.inner.get_mut(metric.series()) {
767 Some(existing) => {
768 let mut new_existing = existing.clone();
771 let (series, data, metadata) = metric.into_parts();
772 if new_existing.data.update(&data) {
773 new_existing.metadata.merge(metadata);
774 new_existing.update_timestamp(timestamp);
775 self.insert_with_tracking(series, new_existing);
776 None
777 } else {
778 warn!(message = "Metric changed type, dropping old value.", %series);
779 Some(Metric::from_parts(series, data, metadata))
780 }
781 }
782 None => Some(metric),
783 }
784 }
785 };
786 if let Some(metric) = update {
787 self.insert(metric, timestamp);
788 }
789 }
790
791 pub fn remove(&mut self, series: &MetricSeries) -> bool {
795 if let Some(entry) = self.inner.pop(series) {
796 if let Some(ref mut capacity_policy) = self.capacity_policy {
797 capacity_policy.free_item(series, &entry);
798 }
799 return true;
800 }
801 false
802 }
803}
804
805impl Default for MetricSet {
806 fn default() -> Self {
807 Self::new(MetricSetSettings::default())
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use vector_lib::event::metric::{MetricKind, MetricValue};
814
815 use super::*;
816
817 fn counter(name: &str, value: f64, kind: MetricKind) -> Metric {
818 Metric::new(name, kind, MetricValue::Counter { value })
819 }
820
821 #[test]
824 fn unbounded_incremental_to_absolute_accumulates() {
825 let mut set = MetricSet::default();
826 assert!(matches!(set.inner, MetricSetInner::Unbounded(_)));
827
828 let out = set.make_absolute(counter("hits", 1.0, MetricKind::Incremental));
830 assert_eq!(out.unwrap().value(), &MetricValue::Counter { value: 1.0 });
831
832 let out = set.make_absolute(counter("hits", 2.0, MetricKind::Incremental));
834 assert_eq!(out.unwrap().value(), &MetricValue::Counter { value: 3.0 });
835
836 let metrics = set.into_metrics();
838 assert_eq!(metrics.len(), 1);
839 assert_eq!(metrics[0].name(), "hits");
840 }
841
842 #[test]
843 fn unbounded_absolute_passes_through() {
844 let mut set = MetricSet::default();
845
846 let out = set.make_absolute(counter("rps", 42.0, MetricKind::Absolute));
847 assert_eq!(out.unwrap().value(), &MetricValue::Counter { value: 42.0 });
848
849 assert!(set.is_empty());
851 }
852
853 #[test]
855 fn bounded_path_selected_when_capacity_policy_set() {
856 let set = MetricSet::new(MetricSetSettings {
857 max_events: Some(10),
858 ..Default::default()
859 });
860 assert!(matches!(set.inner, MetricSetInner::Bounded(_)));
861 }
862}