relay_server/managed/
counted.rs

1use std::collections::BTreeMap;
2
3use itertools::Either;
4use relay_event_schema::protocol::{
5    OurLog, SessionAggregateItem, SessionAggregates, SessionUpdate, Span, SpanV2, TraceMetric,
6};
7use relay_protocol::Annotated;
8use relay_quotas::DataCategory;
9use smallvec::SmallVec;
10
11use crate::envelope::{Item, SourceQuantities, WithHeader};
12use crate::metrics_extraction::ExtractedMetrics;
13use crate::utils::EnvelopeSummary;
14use crate::{Envelope, metrics, processing};
15
16/// A list of data categories and amounts.
17pub type Quantities = SmallVec<[(DataCategory, usize); 2]>;
18
19/// A counted item.
20///
21/// An item may represent multiple categories with different counts at once.
22pub trait Counted {
23    /// Returns the contained item quantities.
24    ///
25    /// Implementation are expected to be pure.
26    fn quantities(&self) -> Quantities;
27}
28
29impl Counted for () {
30    fn quantities(&self) -> Quantities {
31        Quantities::new()
32    }
33}
34
35impl<T: Counted> Counted for Option<T> {
36    fn quantities(&self) -> Quantities {
37        match self {
38            Some(inner) => inner.quantities(),
39            None => Quantities::new(),
40        }
41    }
42}
43
44impl<L, R> Counted for Either<L, R>
45where
46    L: Counted,
47    R: Counted,
48{
49    fn quantities(&self) -> Quantities {
50        match self {
51            Either::Left(value) => value.quantities(),
52            Either::Right(value) => value.quantities(),
53        }
54    }
55}
56
57impl Counted for (DataCategory, usize) {
58    fn quantities(&self) -> Quantities {
59        smallvec::smallvec![*self]
60    }
61}
62
63impl<const N: usize> Counted for [(DataCategory, usize); N] {
64    fn quantities(&self) -> Quantities {
65        smallvec::SmallVec::from_slice(self)
66    }
67}
68
69impl Counted for Item {
70    fn quantities(&self) -> Quantities {
71        self.quantities()
72    }
73}
74
75impl Counted for Box<Envelope> {
76    fn quantities(&self) -> Quantities {
77        EnvelopeSummary::compute(self).quantities()
78    }
79}
80
81impl Counted for EnvelopeSummary {
82    fn quantities(&self) -> Quantities {
83        let mut quantities = Quantities::new();
84
85        if let Some(category) = self.event_category {
86            quantities.push((category, 1));
87            if let Some(category) = category.index_category() {
88                quantities.push((category, 1));
89            }
90        }
91
92        let data = [
93            (DataCategory::Attachment, self.attachment_quantities.bytes()),
94            (
95                DataCategory::AttachmentItem,
96                self.attachment_quantities.count(),
97            ),
98            (DataCategory::Profile, self.profile_quantity.total),
99            (DataCategory::ProfileBackend, self.profile_quantity.backend),
100            (DataCategory::ProfileUi, self.profile_quantity.ui),
101            (DataCategory::ProfileIndexed, self.profile_quantity.total),
102            (DataCategory::Span, self.span_quantity),
103            (DataCategory::SpanIndexed, self.span_quantity),
104            (
105                DataCategory::Transaction,
106                self.secondary_transaction_quantity,
107            ),
108            (DataCategory::Span, self.secondary_span_quantity),
109            (DataCategory::Replay, self.replay_quantity),
110            (DataCategory::ProfileChunk, self.profile_chunk_quantity),
111            (DataCategory::ProfileChunkUi, self.profile_chunk_ui_quantity),
112            (DataCategory::UserReportV2, self.user_report_quantity),
113            (DataCategory::TraceMetric, self.trace_metric_quantity),
114            (
115                DataCategory::TraceMetricByte,
116                self.trace_metric_byte_quantity,
117            ),
118            (DataCategory::LogItem, self.log_item_quantity),
119            (DataCategory::LogByte, self.log_byte_quantity),
120            (DataCategory::Monitor, self.monitor_quantity),
121            (DataCategory::Session, self.session_quantity),
122        ];
123
124        for (category, quantity) in data {
125            if quantity > 0 {
126                quantities.push((category, quantity));
127            }
128        }
129
130        quantities
131    }
132}
133
134impl Counted for WithHeader<OurLog> {
135    fn quantities(&self) -> Quantities {
136        smallvec::smallvec![
137            (DataCategory::LogItem, 1),
138            (
139                DataCategory::LogByte,
140                processing::logs::get_calculated_byte_size(self)
141            )
142        ]
143    }
144}
145
146impl Counted for WithHeader<TraceMetric> {
147    fn quantities(&self) -> Quantities {
148        smallvec::smallvec![
149            (DataCategory::TraceMetric, 1),
150            (
151                DataCategory::TraceMetricByte,
152                processing::trace_metrics::get_calculated_byte_size(self)
153            )
154        ]
155    }
156}
157
158impl Counted for WithHeader<SpanV2> {
159    fn quantities(&self) -> Quantities {
160        smallvec::smallvec![(DataCategory::Span, 1), (DataCategory::SpanIndexed, 1)]
161    }
162}
163
164impl Counted for Annotated<Span> {
165    fn quantities(&self) -> Quantities {
166        smallvec::smallvec![(DataCategory::Span, 1), (DataCategory::SpanIndexed, 1)]
167    }
168}
169
170impl Counted for ExtractedMetrics {
171    fn quantities(&self) -> Quantities {
172        // We only consider project metrics, sampling project metrics should never carry outcomes,
173        // as they would be for a *different* project.
174        let SourceQuantities {
175            transactions,
176            spans,
177            buckets,
178        } = metrics::extract_quantities(&self.project_metrics);
179
180        [
181            (DataCategory::Transaction, transactions),
182            (DataCategory::Span, spans),
183            (DataCategory::MetricBucket, buckets),
184        ]
185        .into_iter()
186        .filter(|(_, q)| *q > 0)
187        .collect()
188    }
189}
190
191impl Counted for SessionUpdate {
192    fn quantities(&self) -> Quantities {
193        smallvec::smallvec![(DataCategory::Session, 1)]
194    }
195}
196
197impl Counted for SessionAggregates {
198    fn quantities(&self) -> Quantities {
199        smallvec::smallvec![(DataCategory::Session, self.aggregates.len())]
200    }
201}
202impl Counted for SessionAggregateItem {
203    fn quantities(&self) -> Quantities {
204        smallvec::smallvec![(DataCategory::Session, 1)]
205    }
206}
207
208#[cfg(feature = "processing")]
209impl Counted for sentry_protos::snuba::v1::Outcomes {
210    fn quantities(&self) -> Quantities {
211        self.category_count
212            .iter()
213            .inspect(|cc| {
214                debug_assert!(DataCategory::try_from(cc.data_category).is_ok());
215                debug_assert!(usize::try_from(cc.quantity).is_ok());
216            })
217            .filter_map(|cc| {
218                Some((
219                    DataCategory::try_from(cc.data_category).ok()?,
220                    usize::try_from(cc.quantity).ok()?,
221                ))
222            })
223            .collect()
224    }
225}
226
227#[cfg(feature = "processing")]
228impl Counted for sentry_protos::snuba::v1::TraceItem {
229    fn quantities(&self) -> Quantities {
230        self.outcomes.quantities()
231    }
232}
233
234impl<T> Counted for &T
235where
236    T: Counted,
237{
238    fn quantities(&self) -> Quantities {
239        (*self).quantities()
240    }
241}
242
243impl<T> Counted for Box<T>
244where
245    T: Counted,
246{
247    fn quantities(&self) -> Quantities {
248        self.as_ref().quantities()
249    }
250}
251
252impl<T: Counted> Counted for [T] {
253    fn quantities(&self) -> Quantities {
254        let mut quantities = BTreeMap::new();
255        for element in self {
256            for (category, size) in element.quantities() {
257                *quantities.entry(category).or_default() += size;
258            }
259        }
260        quantities.into_iter().collect()
261    }
262}
263
264impl<T: Counted> Counted for Vec<T> {
265    fn quantities(&self) -> Quantities {
266        self.as_slice().quantities()
267    }
268}
269
270impl<T: Counted, const N: usize> Counted for SmallVec<[T; N]> {
271    fn quantities(&self) -> Quantities {
272        self.as_slice().quantities()
273    }
274}