relay_metrics/
utils.rs

1use core::fmt;
2use std::collections::BTreeMap;
3use std::ops::{AddAssign, SubAssign};
4
5use relay_base_schema::metrics::MetricNamespace;
6
7/// Estimates the number of bytes needed to encode the tags.
8///
9/// Note that this does not necessarily match the exact memory footprint of the tags,
10/// because data structures or their serialization have overheads.
11pub fn tags_cost(tags: &BTreeMap<String, String>) -> usize {
12    tags.iter().map(|(k, v)| k.len() + v.len()).sum()
13}
14
15/// Utility to store information for each [`MetricNamespace`].
16#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct ByNamespace<T> {
18    /// Value for the [`MetricNamespace::Sessions`] namespace.
19    pub sessions: T,
20    /// Value for the [`MetricNamespace::Transactions`] namespace.
21    pub transactions: T,
22    /// Value for the [`MetricNamespace::Spans`] namespace.
23    pub spans: T,
24    /// Value for the [`MetricNamespace::Custom`] namespace.
25    pub custom: T,
26    /// Value for the [`MetricNamespace::Stats`] namespace.
27    pub stats: T,
28    /// Value for the [`MetricNamespace::Unsupported`] namespace.
29    pub unsupported: T,
30}
31
32impl<T> ByNamespace<T> {
33    /// Returns a reference for the value stored for `namespace`.
34    pub fn get(&self, namespace: MetricNamespace) -> &T {
35        match namespace {
36            MetricNamespace::Sessions => &self.sessions,
37            MetricNamespace::Transactions => &self.transactions,
38            MetricNamespace::Spans => &self.spans,
39            MetricNamespace::Custom => &self.custom,
40            MetricNamespace::Stats => &self.stats,
41            MetricNamespace::Unsupported => &self.unsupported,
42        }
43    }
44
45    /// Returns a mutable reference for the value stored for `namespace`.
46    pub fn get_mut(&mut self, namespace: MetricNamespace) -> &mut T {
47        match namespace {
48            MetricNamespace::Sessions => &mut self.sessions,
49            MetricNamespace::Transactions => &mut self.transactions,
50            MetricNamespace::Spans => &mut self.spans,
51            MetricNamespace::Custom => &mut self.custom,
52            MetricNamespace::Stats => &mut self.stats,
53            MetricNamespace::Unsupported => &mut self.unsupported,
54        }
55    }
56}
57
58impl<T> IntoIterator for ByNamespace<T> {
59    type Item = (MetricNamespace, T);
60    type IntoIter = std::array::IntoIter<(MetricNamespace, T), 6>;
61
62    fn into_iter(self) -> Self::IntoIter {
63        let Self {
64            sessions,
65            transactions,
66            spans,
67            custom,
68            stats,
69            unsupported,
70        } = self;
71
72        [
73            (MetricNamespace::Sessions, sessions),
74            (MetricNamespace::Transactions, transactions),
75            (MetricNamespace::Spans, spans),
76            (MetricNamespace::Custom, custom),
77            (MetricNamespace::Stats, stats),
78            (MetricNamespace::Unsupported, unsupported),
79        ]
80        .into_iter()
81    }
82}
83
84impl<T: fmt::Debug + Default + PartialEq> fmt::Debug for ByNamespace<T> {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        // A more compact representation. Mainly for snapshot testing.
87        write!(f, "(")?;
88
89        let mut values = MetricNamespace::all()
90            .into_iter()
91            .map(|ns| (ns, self.get(ns)))
92            .filter(|(_, v)| v != &&T::default())
93            .enumerate()
94            .peekable();
95
96        match values.peek() {
97            None => write!(f, "{:?}", T::default())?,
98            Some(_) => {
99                for (i, (namespace, value)) in values {
100                    if i > 0 {
101                        write!(f, ", ")?;
102                    }
103                    write!(f, "{namespace}:{value:?}")?;
104                }
105            }
106        }
107
108        write!(f, ")")
109    }
110}
111
112macro_rules! impl_op {
113    ($op:tt, $opfn:ident) => {
114        impl<T: $op> $op for ByNamespace<T> {
115            fn $opfn(&mut self, rhs: Self) {
116                let Self {
117                    sessions,
118                    transactions,
119                    spans,
120                    custom,
121                    stats,
122                    unsupported,
123                } = self;
124
125                $op::$opfn(sessions, rhs.sessions);
126                $op::$opfn(transactions, rhs.transactions);
127                $op::$opfn(spans, rhs.spans);
128                $op::$opfn(custom, rhs.custom);
129                $op::$opfn(stats, rhs.stats);
130                $op::$opfn(unsupported, rhs.unsupported);
131            }
132        }
133    };
134}
135
136impl_op!(AddAssign, add_assign);
137impl_op!(SubAssign, sub_assign);
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_get() {
145        let mut v = ByNamespace::<usize>::default();
146
147        for (i, namespace) in MetricNamespace::all().into_iter().enumerate() {
148            assert_eq!(*v.get(namespace), 0);
149            assert_eq!(*v.get_mut(namespace), 0);
150            *v.get_mut(namespace) += i;
151            assert_eq!(*v.get(namespace), i);
152        }
153
154        // Make sure a getter does not override another value by iterating over all values again.
155        for (i, namespace) in MetricNamespace::all().into_iter().enumerate() {
156            assert_eq!(*v.get(namespace), i);
157            assert_eq!(*v.get_mut(namespace), i);
158        }
159    }
160
161    #[test]
162    fn test_add_sub() {
163        let mut v = ByNamespace::<usize>::default();
164        for (i, namespace) in MetricNamespace::all().into_iter().enumerate() {
165            *v.get_mut(namespace) += i;
166        }
167
168        let mut v2 = v;
169        v2 -= v;
170        assert_eq!(v2, Default::default());
171
172        v2 += v;
173        assert_eq!(v2, v);
174    }
175}