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<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 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}