diff --git a/relay-dynamic-config/src/defaults.rs b/relay-dynamic-config/src/defaults.rs index 758795ab576..8e343ea80c6 100644 --- a/relay-dynamic-config/src/defaults.rs +++ b/relay-dynamic-config/src/defaults.rs @@ -965,39 +965,120 @@ pub fn hardcoded_span_metrics() -> Vec<(GroupKey, Vec, Vec::from_json(json).unwrap().0.unwrap(); + + let performance_score: PerformanceScoreConfig = serde_json::from_value(json!({ + "profiles": [ + { + "name": "Default", + "scoreComponents": [ + { + "measurement": "fcp", + "weight": 0.15, + "p10": 900.0, + "p50": 1600.0, + "optional": true, + }, + { + "measurement": "lcp", + "weight": 0.30, + "p10": 1200.0, + "p50": 2400.0, + "optional": true, + }, + { + "measurement": "cls", + "weight": 0.15, + "p10": 0.1, + "p50": 0.25, + "optional": true, + }, + { + "measurement": "ttfb", + "weight": 0.10, + "p10": 200.0, + "p50": 400.0, + "optional": true, + }, + ], + "condition": { + "op": "and", + "inner": [], + }, + } + ] + })) + .unwrap(); + + normalize( + &mut event, + &mut Meta::default(), + &NormalizationConfig { + performance_score: Some(&performance_score), + ..Default::default() + }, + ); + + insta::assert_ron_snapshot!(SerializableAnnotated(&event.measurements), {}, @r###" + { + "cls": { + "value": 0.5, + "unit": "none", + }, + "score.cls": { + "value": 0.16615877613713903, + "unit": "ratio", + }, + "score.total": { + "value": 0.16615877613713903, + "unit": "ratio", + }, + "score.weight.cls": { + "value": 1.0, + "unit": "ratio", + }, + "score.weight.fcp": { + "value": 0.0, + "unit": "ratio", + }, + "score.weight.lcp": { + "value": 0.0, + "unit": "ratio", + }, + "score.weight.ttfb": { + "value": 0.0, + "unit": "ratio", + }, + } + "###); + } + #[test] fn test_computed_performance_score_uses_first_matching_profile() { let json = r#" diff --git a/relay-event-normalization/src/normalize/span/tag_extraction.rs b/relay-event-normalization/src/normalize/span/tag_extraction.rs index 9dc15454ac9..4b99f7f8bca 100644 --- a/relay-event-normalization/src/normalize/span/tag_extraction.rs +++ b/relay-event-normalization/src/normalize/span/tag_extraction.rs @@ -750,7 +750,9 @@ pub fn extract_tags( } } if let Some(measurements) = span.measurements.value() { - if span_op.starts_with("ui.interaction.") && measurements.contains_key("inp") { + if (span_op.starts_with("ui.interaction.") && measurements.contains_key("inp")) + || span_op.starts_with("ui.webvital.") + { if let Some(transaction) = span .data .value() diff --git a/relay-server/src/metrics_extraction/event.rs b/relay-server/src/metrics_extraction/event.rs index 0f5154c37ac..8e3764e83d1 100644 --- a/relay-server/src/metrics_extraction/event.rs +++ b/relay-server/src/metrics_extraction/event.rs @@ -1653,7 +1653,7 @@ mod tests { } #[test] - fn test_extract_span_metrics_performance_score() { + fn test_extract_span_metrics_inp_performance_score() { let json = r#" { "op": "ui.interaction.click", @@ -1693,6 +1693,46 @@ mod tests { } } + #[test] + fn test_extract_span_metrics_cls_performance_score() { + let json = r#" + { + "op": "ui.webvital.cls", + "span_id": "bd429c44b67a3eb4", + "start_timestamp": 1597976302.0000000, + "timestamp": 1597976302.0000000, + "exclusive_time": 0, + "trace_id": "ff62a8b040f340bda5d830223def1d81", + "sentry_tags": { + "browser.name": "Chrome", + "op": "ui.webvital.cls" + }, + "measurements": { + "score.total": {"value": 1.0}, + "score.cls": {"value": 1.0}, + "score.weight.cls": {"value": 1.0}, + "cls": {"value": 1.0} + } + } + "#; + let span = Annotated::::from_json(json).unwrap(); + let metrics = generic::extract_metrics( + span.value().unwrap(), + combined_config([Feature::ExtractCommonSpanMetricsFromEvent], None).combined(), + ); + + for mri in [ + "d:transactions/measurements.cls@ratio", + "d:transactions/measurements.score.cls@ratio", + "d:transactions/measurements.score.total@ratio", + "d:transactions/measurements.score.weight.cls@ratio", + ] { + assert!(metrics.iter().any(|b| &*b.name == mri)); + assert!(metrics.iter().any(|b| b.tags.contains_key("browser.name"))); + assert!(metrics.iter().any(|b| b.tags.contains_key("span.op"))); + } + } + #[test] fn extracts_span_metrics_from_transaction() { let event = r#" diff --git a/relay-server/src/services/processor/span/processing.rs b/relay-server/src/services/processor/span/processing.rs index 6a2e6eee0e4..22decf6bf8c 100644 --- a/relay-server/src/services/processor/span/processing.rs +++ b/relay-server/src/services/processor/span/processing.rs @@ -384,9 +384,9 @@ impl<'a> NormalizeSpanConfig<'a> { fn set_segment_attributes(span: &mut Annotated) { let Some(span) = span.value_mut() else { return }; - // Identify INP spans and make sure they are not wrapped in a segment. + // Identify INP spans or other WebVital spans and make sure they are not wrapped in a segment. if let Some(span_op) = span.op.value() { - if span_op.starts_with("ui.interaction.") { + if span_op.starts_with("ui.interaction.") || span_op.starts_with("ui.webvital.") { span.is_segment = None.into(); span.parent_span_id = None.into(); span.segment_id = None.into();