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
16pub type Quantities = SmallVec<[(DataCategory, usize); 2]>;
18
19pub trait Counted {
23 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 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}