Skip to content

Commit 0f12d97

Browse files
Calculates perf score on standalone cls spans, and extracts metrics
1 parent d006e43 commit 0f12d97

File tree

5 files changed

+261
-37
lines changed

5 files changed

+261
-37
lines changed

relay-dynamic-config/src/defaults.rs

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -965,39 +965,120 @@ pub fn hardcoded_span_metrics() -> Vec<(GroupKey, Vec<MetricSpec>, Vec<TagMappin
965965
),
966966
(
967967
GroupKey::SpanMetricsTx,
968-
vec![MetricSpec {
969-
category: DataCategory::Span,
970-
mri: "d:transactions/measurements.score.total@ratio".into(),
971-
field: Some("span.measurements.score.total.value".into()),
972-
condition: Some(
973-
// If transactions are extracted from spans, the transaction processing pipeline
974-
// will take care of this metric.
975-
is_allowed_browser.clone() & RuleCondition::eq("span.was_transaction", false),
976-
),
977-
tags: vec![
978-
Tag::with_key("span.op")
979-
.from_field("span.sentry_tags.op")
980-
.always(),
981-
Tag::with_key("transaction.op")
982-
.from_field("span.sentry_tags.transaction.op")
983-
.always(),
984-
Tag::with_key("transaction")
985-
.from_field("span.sentry_tags.transaction")
986-
.always(),
987-
Tag::with_key("environment")
988-
.from_field("span.sentry_tags.environment")
989-
.always(),
990-
Tag::with_key("release")
991-
.from_field("span.sentry_tags.release")
992-
.always(),
993-
Tag::with_key("browser.name")
994-
.from_field("span.browser.name")
995-
.always(), // already guarded by condition on metric
996-
Tag::with_key("user.geo.subregion")
997-
.from_field("span.sentry_tags.user.geo.subregion")
998-
.always(), // already guarded by condition on metric
999-
],
1000-
}],
968+
vec![
969+
MetricSpec {
970+
category: DataCategory::Span,
971+
mri: "d:transactions/measurements.score.total@ratio".into(),
972+
field: Some("span.measurements.score.total.value".into()),
973+
condition: Some(
974+
// If transactions are extracted from spans, the transaction processing pipeline
975+
// will take care of this metric.
976+
is_allowed_browser.clone()
977+
& RuleCondition::eq("span.was_transaction", false),
978+
),
979+
tags: vec![
980+
Tag::with_key("span.op")
981+
.from_field("span.sentry_tags.op")
982+
.always(),
983+
Tag::with_key("transaction.op")
984+
.from_field("span.sentry_tags.transaction.op")
985+
.always(),
986+
Tag::with_key("transaction")
987+
.from_field("span.sentry_tags.transaction")
988+
.always(),
989+
Tag::with_key("environment")
990+
.from_field("span.sentry_tags.environment")
991+
.always(),
992+
Tag::with_key("release")
993+
.from_field("span.sentry_tags.release")
994+
.always(),
995+
Tag::with_key("browser.name")
996+
.from_field("span.browser.name")
997+
.always(), // already guarded by condition on metric
998+
Tag::with_key("user.geo.subregion")
999+
.from_field("span.sentry_tags.user.geo.subregion")
1000+
.always(), // already guarded by condition on metric
1001+
],
1002+
},
1003+
MetricSpec {
1004+
category: DataCategory::Span,
1005+
mri: "d:transactions/measurements.score.cls@ratio".into(),
1006+
field: Some("span.measurements.score.cls.value".into()),
1007+
condition: Some(is_allowed_browser.clone()),
1008+
tags: vec![
1009+
Tag::with_key("span.op")
1010+
.from_field("span.sentry_tags.op")
1011+
.always(),
1012+
Tag::with_key("transaction")
1013+
.from_field("span.sentry_tags.transaction")
1014+
.always(),
1015+
Tag::with_key("environment")
1016+
.from_field("span.sentry_tags.environment")
1017+
.always(),
1018+
Tag::with_key("release")
1019+
.from_field("span.sentry_tags.release")
1020+
.always(),
1021+
Tag::with_key("browser.name")
1022+
.from_field("span.sentry_tags.browser.name")
1023+
.always(), // already guarded by condition on metric
1024+
Tag::with_key("user.geo.subregion")
1025+
.from_field("span.sentry_tags.user.geo.subregion")
1026+
.always(), // already guarded by condition on metric
1027+
],
1028+
},
1029+
MetricSpec {
1030+
category: DataCategory::Span,
1031+
mri: "d:transactions/measurements.score.weight.cls@ratio".into(),
1032+
field: Some("span.measurements.score.weight.cls.value".into()),
1033+
condition: Some(is_allowed_browser.clone()),
1034+
tags: vec![
1035+
Tag::with_key("span.op")
1036+
.from_field("span.sentry_tags.op")
1037+
.always(),
1038+
Tag::with_key("transaction")
1039+
.from_field("span.sentry_tags.transaction")
1040+
.always(),
1041+
Tag::with_key("environment")
1042+
.from_field("span.sentry_tags.environment")
1043+
.always(),
1044+
Tag::with_key("release")
1045+
.from_field("span.sentry_tags.release")
1046+
.always(),
1047+
Tag::with_key("browser.name")
1048+
.from_field("span.sentry_tags.browser.name")
1049+
.always(), // already guarded by condition on metric
1050+
Tag::with_key("user.geo.subregion")
1051+
.from_field("span.sentry_tags.user.geo.subregion")
1052+
.always(), // already guarded by condition on metric
1053+
],
1054+
},
1055+
MetricSpec {
1056+
category: DataCategory::Span,
1057+
mri: "d:transactions/measurements.cls@ratio".into(),
1058+
field: Some("span.measurements.cls.value".into()),
1059+
condition: Some(is_allowed_browser.clone()),
1060+
tags: vec![
1061+
Tag::with_key("span.op")
1062+
.from_field("span.sentry_tags.op")
1063+
.always(),
1064+
Tag::with_key("transaction")
1065+
.from_field("span.sentry_tags.transaction")
1066+
.always(),
1067+
Tag::with_key("environment")
1068+
.from_field("span.sentry_tags.environment")
1069+
.always(),
1070+
Tag::with_key("release")
1071+
.from_field("span.sentry_tags.release")
1072+
.always(),
1073+
Tag::with_key("browser.name")
1074+
.from_field("span.sentry_tags.browser.name")
1075+
.always(), // already guarded by condition on metric
1076+
Tag::with_key("user.geo.subregion")
1077+
.from_field("span.sentry_tags.user.geo.subregion")
1078+
.always(), // already guarded by condition on metric
1079+
],
1080+
},
1081+
],
10011082
vec![],
10021083
),
10031084
]

relay-event-normalization/src/event.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3381,6 +3381,107 @@ mod tests {
33813381
"###);
33823382
}
33833383

3384+
#[test]
3385+
fn test_computes_standalone_cls_performance_score() {
3386+
let json = r#"
3387+
{
3388+
"type": "transaction",
3389+
"timestamp": "2021-04-26T08:00:05+0100",
3390+
"start_timestamp": "2021-04-26T08:00:00+0100",
3391+
"measurements": {
3392+
"cls": {"value": 0.5}
3393+
}
3394+
}
3395+
"#;
3396+
3397+
let mut event = Annotated::<Event>::from_json(json).unwrap().0.unwrap();
3398+
3399+
let performance_score: PerformanceScoreConfig = serde_json::from_value(json!({
3400+
"profiles": [
3401+
{
3402+
"name": "Default",
3403+
"scoreComponents": [
3404+
{
3405+
"measurement": "fcp",
3406+
"weight": 0.15,
3407+
"p10": 900.0,
3408+
"p50": 1600.0,
3409+
"optional": true,
3410+
},
3411+
{
3412+
"measurement": "lcp",
3413+
"weight": 0.30,
3414+
"p10": 1200.0,
3415+
"p50": 2400.0,
3416+
"optional": true,
3417+
},
3418+
{
3419+
"measurement": "cls",
3420+
"weight": 0.15,
3421+
"p10": 0.1,
3422+
"p50": 0.25,
3423+
"optional": true,
3424+
},
3425+
{
3426+
"measurement": "ttfb",
3427+
"weight": 0.10,
3428+
"p10": 200.0,
3429+
"p50": 400.0,
3430+
"optional": true,
3431+
},
3432+
],
3433+
"condition": {
3434+
"op": "and",
3435+
"inner": [],
3436+
},
3437+
}
3438+
]
3439+
}))
3440+
.unwrap();
3441+
3442+
normalize(
3443+
&mut event,
3444+
&mut Meta::default(),
3445+
&NormalizationConfig {
3446+
performance_score: Some(&performance_score),
3447+
..Default::default()
3448+
},
3449+
);
3450+
3451+
insta::assert_ron_snapshot!(SerializableAnnotated(&event.measurements), {}, @r###"
3452+
{
3453+
"cls": {
3454+
"value": 0.5,
3455+
"unit": "none",
3456+
},
3457+
"score.cls": {
3458+
"value": 0.16615877613713903,
3459+
"unit": "ratio",
3460+
},
3461+
"score.total": {
3462+
"value": 0.16615877613713903,
3463+
"unit": "ratio",
3464+
},
3465+
"score.weight.cls": {
3466+
"value": 1.0,
3467+
"unit": "ratio",
3468+
},
3469+
"score.weight.fcp": {
3470+
"value": 0.0,
3471+
"unit": "ratio",
3472+
},
3473+
"score.weight.lcp": {
3474+
"value": 0.0,
3475+
"unit": "ratio",
3476+
},
3477+
"score.weight.ttfb": {
3478+
"value": 0.0,
3479+
"unit": "ratio",
3480+
},
3481+
}
3482+
"###);
3483+
}
3484+
33843485
#[test]
33853486
fn test_computed_performance_score_uses_first_matching_profile() {
33863487
let json = r#"

relay-event-normalization/src/normalize/span/tag_extraction.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,9 @@ pub fn extract_tags(
750750
}
751751
}
752752
if let Some(measurements) = span.measurements.value() {
753-
if span_op.starts_with("ui.interaction.") && measurements.contains_key("inp") {
753+
if (span_op.starts_with("ui.interaction.") && measurements.contains_key("inp"))
754+
|| span_op.starts_with("ui.webvital.")
755+
{
754756
if let Some(transaction) = span
755757
.data
756758
.value()

relay-server/src/metrics_extraction/event.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1653,7 +1653,7 @@ mod tests {
16531653
}
16541654

16551655
#[test]
1656-
fn test_extract_span_metrics_performance_score() {
1656+
fn test_extract_span_metrics_inp_performance_score() {
16571657
let json = r#"
16581658
{
16591659
"op": "ui.interaction.click",
@@ -1693,6 +1693,46 @@ mod tests {
16931693
}
16941694
}
16951695

1696+
#[test]
1697+
fn test_extract_span_metrics_cls_performance_score() {
1698+
let json = r#"
1699+
{
1700+
"op": "ui.webvital.cls",
1701+
"span_id": "bd429c44b67a3eb4",
1702+
"start_timestamp": 1597976302.0000000,
1703+
"timestamp": 1597976302.0000000,
1704+
"exclusive_time": 0,
1705+
"trace_id": "ff62a8b040f340bda5d830223def1d81",
1706+
"sentry_tags": {
1707+
"browser.name": "Chrome",
1708+
"op": "ui.webvital.cls"
1709+
},
1710+
"measurements": {
1711+
"score.total": {"value": 1.0},
1712+
"score.cls": {"value": 1.0},
1713+
"score.weight.cls": {"value": 1.0},
1714+
"cls": {"value": 1.0}
1715+
}
1716+
}
1717+
"#;
1718+
let span = Annotated::<Span>::from_json(json).unwrap();
1719+
let metrics = generic::extract_metrics(
1720+
span.value().unwrap(),
1721+
combined_config([Feature::ExtractCommonSpanMetricsFromEvent], None).combined(),
1722+
);
1723+
1724+
for mri in [
1725+
"d:transactions/measurements.cls@ratio",
1726+
"d:transactions/measurements.score.cls@ratio",
1727+
"d:transactions/measurements.score.total@ratio",
1728+
"d:transactions/measurements.score.weight.cls@ratio",
1729+
] {
1730+
assert!(metrics.iter().any(|b| &*b.name == mri));
1731+
assert!(metrics.iter().any(|b| b.tags.contains_key("browser.name")));
1732+
assert!(metrics.iter().any(|b| b.tags.contains_key("span.op")));
1733+
}
1734+
}
1735+
16961736
#[test]
16971737
fn extracts_span_metrics_from_transaction() {
16981738
let event = r#"

relay-server/src/services/processor/span/processing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,9 @@ impl<'a> NormalizeSpanConfig<'a> {
384384
fn set_segment_attributes(span: &mut Annotated<Span>) {
385385
let Some(span) = span.value_mut() else { return };
386386

387-
// Identify INP spans and make sure they are not wrapped in a segment.
387+
// Identify INP spans or other WebVital spans and make sure they are not wrapped in a segment.
388388
if let Some(span_op) = span.op.value() {
389-
if span_op.starts_with("ui.interaction.") {
389+
if span_op.starts_with("ui.interaction.") || span_op.starts_with("ui.webvital.") {
390390
span.is_segment = None.into();
391391
span.parent_span_id = None.into();
392392
span.segment_id = None.into();

0 commit comments

Comments
 (0)