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::transactions::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 Counted for Item {
64 fn quantities(&self) -> Quantities {
65 self.quantities()
66 }
67}
68
69impl Counted for Box<Envelope> {
70 fn quantities(&self) -> Quantities {
71 let mut quantities = Quantities::new();
72
73 let summary = EnvelopeSummary::compute(self);
75 if let Some(category) = summary.event_category {
76 quantities.push((category, 1));
77 if let Some(category) = category.index_category() {
78 quantities.push((category, 1));
79 }
80 }
81
82 let data = [
83 (
84 DataCategory::Attachment,
85 summary.attachment_quantities.bytes(),
86 ),
87 (
88 DataCategory::AttachmentItem,
89 summary.attachment_quantities.count(),
90 ),
91 (DataCategory::Profile, summary.profile_quantity),
92 (DataCategory::ProfileIndexed, summary.profile_quantity),
93 (DataCategory::Span, summary.span_quantity),
94 (DataCategory::SpanIndexed, summary.span_quantity),
95 (
96 DataCategory::Transaction,
97 summary.secondary_transaction_quantity,
98 ),
99 (DataCategory::Span, summary.secondary_span_quantity),
100 (DataCategory::Replay, summary.replay_quantity),
101 (DataCategory::ProfileChunk, summary.profile_chunk_quantity),
102 (
103 DataCategory::ProfileChunkUi,
104 summary.profile_chunk_ui_quantity,
105 ),
106 (DataCategory::UserReportV2, summary.user_report_quantity),
107 (DataCategory::TraceMetric, summary.trace_metric_quantity),
108 (DataCategory::LogItem, summary.log_item_quantity),
109 (DataCategory::LogByte, summary.log_byte_quantity),
110 (DataCategory::Monitor, summary.monitor_quantity),
111 (DataCategory::Session, summary.session_quantity),
112 ];
113
114 for (category, quantity) in data {
115 if quantity > 0 {
116 quantities.push((category, quantity));
117 }
118 }
119
120 quantities
121 }
122}
123
124impl Counted for WithHeader<OurLog> {
125 fn quantities(&self) -> Quantities {
126 smallvec::smallvec![
127 (DataCategory::LogItem, 1),
128 (
129 DataCategory::LogByte,
130 processing::logs::get_calculated_byte_size(self)
131 )
132 ]
133 }
134}
135
136impl Counted for WithHeader<TraceMetric> {
137 fn quantities(&self) -> Quantities {
138 smallvec::smallvec![(DataCategory::TraceMetric, 1)]
139 }
140}
141
142impl Counted for WithHeader<SpanV2> {
143 fn quantities(&self) -> Quantities {
144 smallvec::smallvec![(DataCategory::Span, 1), (DataCategory::SpanIndexed, 1)]
145 }
146}
147
148impl Counted for Annotated<Span> {
149 fn quantities(&self) -> Quantities {
150 smallvec::smallvec![(DataCategory::Span, 1), (DataCategory::SpanIndexed, 1)]
151 }
152}
153
154impl Counted for ExtractedMetrics {
155 fn quantities(&self) -> Quantities {
156 let SourceQuantities {
159 transactions,
160 spans,
161 profiles,
162 buckets,
163 } = metrics::extract_quantities(&self.project_metrics);
164
165 [
166 (DataCategory::Transaction, transactions),
167 (DataCategory::Span, spans),
168 (DataCategory::Profile, profiles),
169 (DataCategory::MetricBucket, buckets),
170 ]
171 .into_iter()
172 .filter(|(_, q)| *q > 0)
173 .collect()
174 }
175}
176
177impl Counted for SessionUpdate {
178 fn quantities(&self) -> Quantities {
179 smallvec::smallvec![(DataCategory::Session, 1)]
180 }
181}
182
183impl Counted for SessionAggregates {
184 fn quantities(&self) -> Quantities {
185 smallvec::smallvec![(DataCategory::Session, self.aggregates.len())]
186 }
187}
188impl Counted for SessionAggregateItem {
189 fn quantities(&self) -> Quantities {
190 smallvec::smallvec![(DataCategory::Session, 1)]
191 }
192}
193
194impl<T> Counted for &T
195where
196 T: Counted,
197{
198 fn quantities(&self) -> Quantities {
199 (*self).quantities()
200 }
201}
202
203impl<T> Counted for Box<T>
204where
205 T: Counted,
206{
207 fn quantities(&self) -> Quantities {
208 self.as_ref().quantities()
209 }
210}
211
212impl<T: Counted> Counted for Vec<T> {
213 fn quantities(&self) -> Quantities {
214 let mut quantities = BTreeMap::new();
215 for element in self {
216 for (category, size) in element.quantities() {
217 *quantities.entry(category).or_default() += size;
218 }
219 }
220 quantities.into_iter().collect()
221 }
222}
223
224impl<T: Counted, const N: usize> Counted for SmallVec<[T; N]> {
225 fn quantities(&self) -> Quantities {
226 let mut quantities = BTreeMap::new();
227 for element in self {
228 for (category, size) in element.quantities() {
229 *quantities.entry(category).or_default() += size;
230 }
231 }
232 quantities.into_iter().collect()
233 }
234}