relay_server/services/processor/
profile.rs1use relay_dynamic_config::Feature;
4
5use relay_config::Config;
6use relay_profiling::ProfileError;
7
8use crate::envelope::ItemType;
9use crate::managed::{ItemAction, TypedEnvelope};
10use crate::services::outcome::{DiscardReason, Outcome};
11use crate::services::processor::should_filter;
12use crate::services::projects::project::ProjectInfo;
13
14pub fn filter<Group>(
15 managed_envelope: &mut TypedEnvelope<Group>,
16 config: &Config,
17 project_info: &ProjectInfo,
18) {
19 let profiling_disabled = should_filter(config, project_info, Feature::Profiling);
20
21 let mut saw_profile = false;
22 managed_envelope.retain_items(|item| match item.ty() {
23 ItemType::Profile if profiling_disabled => ItemAction::DropSilently,
24 ItemType::Profile if !saw_profile => {
26 let profile_allowed = !item.sampled();
29 if !profile_allowed {
30 return ItemAction::DropSilently;
31 }
32
33 match relay_profiling::parse_metadata(&item.payload()) {
34 Ok(_) => {
35 saw_profile = true;
36 ItemAction::Keep
37 }
38 Err(err) => ItemAction::Drop(Outcome::Invalid(DiscardReason::Profiling(
39 relay_profiling::discard_reason(&err),
40 ))),
41 }
42 }
43 ItemType::Profile => ItemAction::Drop(Outcome::Invalid(DiscardReason::Profiling(
45 relay_profiling::discard_reason(&ProfileError::TooManyProfiles),
46 ))),
47 _ => ItemAction::Keep,
48 });
49}
50
51#[cfg(test)]
52mod tests {
53 use crate::envelope::{ContentType, Envelope, Item};
54 use crate::extractors::RequestMeta;
55 use crate::managed::ManagedEnvelope;
56 use crate::processing::{self, Outputs};
57 use crate::services::processor::{ProcessEnvelopeGrouped, ProcessingGroup};
58 use crate::services::processor::{ProcessingError, Submit};
59 use crate::services::projects::project::ProjectInfo;
60 use crate::testutils::create_test_processor;
61 use insta::assert_debug_snapshot;
62 use relay_dynamic_config::{ErrorBoundary, Feature, GlobalConfig, TransactionMetricsConfig};
63 use relay_event_schema::protocol::{Event, EventId, ProfileContext};
64 use relay_protocol::Annotated;
65 use relay_system::Addr;
66
67 use super::*;
68
69 async fn process_event(envelope: Box<Envelope>) -> Result<Annotated<Event>, ProcessingError> {
70 let config = Config::from_json_value(serde_json::json!({
71 "processing": {
72 "enabled": true,
73 "kafka_config": []
74 }
75 }))
76 .unwrap();
77 let processor = create_test_processor(config).await;
78 let mut envelopes = ProcessingGroup::split_envelope(*envelope, &Default::default());
79 assert_eq!(envelopes.len(), 1);
80 let (group, envelope) = envelopes.pop().unwrap();
81
82 let envelope = ManagedEnvelope::new(envelope, Addr::dummy());
83
84 let mut project_info = ProjectInfo::default().sanitized(false);
85 project_info.config.transaction_metrics =
86 Some(ErrorBoundary::Ok(TransactionMetricsConfig::new()));
87 project_info.config.features.0.insert(Feature::Profiling);
88
89 let global_config = GlobalConfig::default();
90 let message = ProcessEnvelopeGrouped {
91 group,
92 envelope,
93 ctx: processing::Context {
94 config: &processor.inner.config,
95 project_info: &project_info,
96 global_config: &global_config,
97 ..processing::Context::for_test()
98 },
99 };
100
101 let result = processor.process(message).await?;
102
103 let Some(Submit::Output {
104 output: Outputs::Transactions(t),
105 ctx: _,
106 }) = result
107 else {
108 panic!();
109 };
110 Ok(t.event().unwrap())
111 }
112
113 #[tokio::test]
114 async fn test_profile_id_transfered() {
115 relay_log::init_test!();
116
117 let event_id = EventId::new();
118 let dsn = "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"
119 .parse()
120 .unwrap();
121 let request_meta = RequestMeta::new(dsn);
122 let mut envelope = Envelope::from_request(Some(event_id), request_meta);
123
124 envelope.add_item({
126 let mut item = Item::new(ItemType::Transaction);
127
128 item.set_payload(
129 ContentType::Json,
130 r#"{
131 "event_id": "9b73438f70e044ecbd006b7fd15b7373",
132 "type": "transaction",
133 "transaction": "/foo/",
134 "timestamp": 946684810.0,
135 "start_timestamp": 946684800.0,
136 "contexts": {
137 "trace": {
138 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
139 "span_id": "fa90fdead5f74053",
140 "op": "http.server",
141 "type": "trace"
142 }
143 },
144 "transaction_info": {
145 "source": "url"
146 }
147 }"#,
148 );
149 item
150 });
151
152 envelope.add_item({
154 let mut item = Item::new(ItemType::Profile);
155 item.set_payload(
156 ContentType::Json,
157 r#"{
158 "profile_id": "012d836b15bb49d7bbf99e64295d995b",
159 "version": "1",
160 "platform": "android",
161 "os": {"name": "foo", "version": "bar"},
162 "device": {"architecture": "zap"},
163 "timestamp": "2023-10-10 00:00:00Z",
164 "profile": {
165 "samples":[
166 {
167 "stack_id":0,
168 "elapsed_since_start_ns":1,
169 "thread_id":1
170 },
171 {
172 "stack_id":0,
173 "elapsed_since_start_ns":2,
174 "thread_id":1
175 }
176 ],
177 "stacks":[[0]],
178 "frames":[{
179 "function":"main"
180 }]
181 },
182 "transactions": [
183 {
184 "id": "9b73438f70e044ecbd006b7fd15b7373",
185 "name": "/foo/",
186 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2"
187 }
188 ]
189 }"#,
190 );
191 item
192 });
193
194 let event = process_event(envelope).await.unwrap();
195
196 let context = event.value().unwrap().context::<ProfileContext>().unwrap();
197
198 assert_debug_snapshot!(context, @r###"
199 ProfileContext {
200 profile_id: EventId(
201 012d836b-15bb-49d7-bbf9-9e64295d995b,
202 ),
203 profiler_id: ~,
204 }
205 "###);
206 }
207
208 #[tokio::test]
209 async fn test_invalid_profile_id_not_transfered() {
210 let event_id = EventId::new();
212 let dsn = "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"
213 .parse()
214 .unwrap();
215 let request_meta = RequestMeta::new(dsn);
216 let mut envelope = Envelope::from_request(Some(event_id), request_meta);
217
218 envelope.add_item({
220 let mut item = Item::new(ItemType::Transaction);
221
222 item.set_payload(
223 ContentType::Json,
224 r#"{
225 "event_id": "9b73438f70e044ecbd006b7fd15b7373",
226 "type": "transaction",
227 "transaction": "/foo/",
228 "timestamp": 946684810.0,
229 "start_timestamp": 946684800.0,
230 "contexts": {
231 "trace": {
232 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
233 "span_id": "fa90fdead5f74053",
234 "op": "http.server",
235 "type": "trace"
236 }
237 },
238 "transaction_info": {
239 "source": "url"
240 }
241 }"#,
242 );
243 item
244 });
245
246 envelope.add_item({
248 let mut item = Item::new(ItemType::Profile);
249 item.set_payload(
250 ContentType::Json,
251 r#"{
252 "profile_id": "012d836b15bb49d7bbf99e64295d995b",
253 "version": "1",
254 "platform": "android",
255 "os": {"name": "foo", "version": "bar"},
256 "device": {"architecture": "zap"},
257 "timestamp": "2023-10-10 00:00:00Z",
258 "profile": {
259 "samples":[
260 {
261 "stack_id":0,
262 "elapsed_since_start_ns":1,
263 "thread_id":1
264 },
265 {
266 "stack_id":1,
267 "elapsed_since_start_ns":2,
268 "thread_id":1
269 }
270 ],
271 "stacks":[[0],[]],
272 "frames":[{
273 "function":"main"
274 }]
275 },
276 "transactions": [
277 {
278 "id": "9b73438f70e044ecbd006b7fd15b7373",
279 "name": "/foo/",
280 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2"
281 }
282 ]
283 }"#,
284 );
285 item
286 });
287
288 let event = process_event(envelope).await.unwrap();
289 let context = event.value().unwrap().context::<ProfileContext>().unwrap();
290
291 assert_debug_snapshot!(context, @r###"
292 ProfileContext {
293 profile_id: ~,
294 profiler_id: ~,
295 }
296 "###);
297 }
298
299 #[tokio::test]
300 async fn filter_standalone_profile() {
301 relay_log::init_test!();
302 let event_id = EventId::new();
304 let dsn = "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"
305 .parse()
306 .unwrap();
307 let request_meta = RequestMeta::new(dsn);
308 let mut envelope = Envelope::from_request(Some(event_id), request_meta);
309
310 envelope.add_item({
312 let mut item = Item::new(ItemType::Profile);
313 item.set_payload(
314 ContentType::Json,
315 r#"{
316 "profile_id": "012d836b15bb49d7bbf99e64295d995b",
317 "version": "1",
318 "platform": "android",
319 "os": {"name": "foo", "version": "bar"},
320 "device": {"architecture": "zap"},
321 "timestamp": "2023-10-10 00:00:00Z"
322 }"#,
323 );
324 item
325 });
326
327 let event = process_event(envelope).await;
328 assert!(matches!(
329 event.unwrap_err(),
330 ProcessingError::ProcessingFailure
331 ));
332 }
333
334 #[tokio::test]
335 async fn test_profile_id_removed_profiler_id_kept() {
336 let event_id = EventId::new();
337 let dsn = "https://e12d836b15bb49d7bbf99e64295d995b:@sentry.io/42"
338 .parse()
339 .unwrap();
340 let request_meta = RequestMeta::new(dsn);
341 let mut envelope = Envelope::from_request(Some(event_id), request_meta);
342
343 envelope.add_item({
345 let mut item = Item::new(ItemType::Transaction);
346
347 item.set_payload(
348 ContentType::Json,
349 r#"{
350 "type": "transaction",
351 "transaction": "/foo/",
352 "timestamp": 946684810.0,
353 "start_timestamp": 946684800.0,
354 "contexts": {
355 "trace": {
356 "trace_id": "4c79f60c11214eb38604f4ae0781bfb2",
357 "span_id": "fa90fdead5f74053",
358 "op": "http.server",
359 "type": "trace"
360 },
361 "profile": {
362 "profile_id": "4c79f60c11214eb38604f4ae0781bfb2",
363 "profiler_id": "4c79f60c11214eb38604f4ae0781bfb2",
364 "type": "profile"
365 }
366 },
367 "transaction_info": {
368 "source": "url"
369 }
370 }"#,
371 );
372 item
373 });
374
375 let mut project_info = ProjectInfo::default();
376 project_info.config.features.0.insert(Feature::Profiling);
377
378 let event = process_event(envelope).await.unwrap();
379 let context = event.value().unwrap().context::<ProfileContext>().unwrap();
380
381 assert_debug_snapshot!(context, @r###"
382 ProfileContext {
383 profile_id: ~,
384 profiler_id: EventId(
385 4c79f60c-1121-4eb3-8604-f4ae0781bfb2,
386 ),
387 }
388 "###);
389 }
390}