1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
use std::fmt::Debug; use std::time::Duration; use std::{collections::HashMap, ops::DerefMut}; use crate::{kind::MetricKindMask, Generation, Generational, Hashable, MetricKind, Registry}; use parking_lot::Mutex; use quanta::{Clock, Instant}; /// Tracks recency of metric updates by their registry generation and time. /// /// In many cases, a user may have a long-running process where metrics are stored over time using /// labels that change for some particular reason, leaving behind versions of that metric with /// labels that are no longer relevant to the current process state. This can lead to cases where /// metrics that no longer matter are still present in rendered output, adding bloat. /// /// When coupled with [`Registry`](crate::Registry), [`Recency`] can be used to track when the last /// update to a metric has occurred for the purposes of removing idle metrics from the registry. In /// addition, it will remove the value from the registry itself to reduce the aforementioned bloat. /// /// [`Recency`] is separate from [`Registry`](crate::Registry) specifically to avoid imposing any /// slowdowns when tracking recency does not matter, despite their otherwise tight coupling. #[derive(Debug)] pub struct Recency<K> { mask: MetricKindMask, inner: Mutex<(Clock, HashMap<K, (Generation, Instant)>)>, idle_timeout: Option<Duration>, } impl<K> Recency<K> { /// Creates a new [`Recency`]. /// /// If `idle_timeout` is `None`, no recency checking will occur. `mask` controls which metrics /// are covered by the recency logic. For example, if `mask` only contains counters and /// histograms, then gauges will not be considered for recency, and thus will never be deleted. /// /// If `idle_timeout` is not `None`, then metrics which have not been updated within the given /// duration will be subject to deletion when checked. Specifically, the deletions done by this /// object only happen when the object is "driven" by calling /// [`should_store`](Recency::should_store), and so handles will not necessarily be deleted /// immediately after execeeding their idle timeout. /// /// Refer to the documentation for [`MetricKindMask`](crate::MetricKindMask) for more /// information on defining a metric kind mask. pub fn new(clock: Clock, mask: MetricKindMask, idle_timeout: Option<Duration>) -> Recency<K> { Recency { mask, inner: Mutex::new((clock, HashMap::new())), idle_timeout, } } /// Checks if the given key should be stored, based on its known recency. /// /// `kind` will be used to see if the given metric kind is subject to recency tracking for this /// instance. `generation` should be obtained from the the same reference to `registry` that /// been given. /// /// If the given key has been updated recently enough, and should continue to be stored, this /// method will return `true` and will update the last update time internally. If the given key /// has not been updated recently enough, the key will be removed from the given registry if the /// given generation also matches. /// /// If the generation does not match, this indicates that the key was updated between querying /// it from the registry and calling this method, and this method will return `true` in those /// cases, and `false` for all remaining cases. pub fn should_store<H, G>( &self, kind: MetricKind, key: &K, gen: Generation, registry: &Registry<K, H, G>, ) -> bool where K: Debug + Eq + Hashable + Clone + 'static, H: Debug + Clone + 'static, G: Generational<H>, { if let Some(idle_timeout) = self.idle_timeout { if self.mask.matches(kind) { let mut guard = self.inner.lock(); let (clock, entries) = guard.deref_mut(); let now = clock.now(); if let Some((last_gen, last_update)) = entries.get_mut(&key) { // If the value is the same as the latest value we have internally, and // we're over the idle timeout period, then remove it and continue. if *last_gen == gen { if (now - *last_update) > idle_timeout { // If the delete returns false, that means that our generation counter is // out-of-date, and that the metric has been updated since, so we don't // actually want to delete it yet. if registry.delete_with_gen(kind, &key, gen) { return false; } } } else { // Value has changed, so mark it such. *last_update = now; } } else { entries.insert(key.clone(), (gen, now)); } } } true } }