Skip to main content

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