Skip to content

Commit

Permalink
feat(spans): Extract INP metrics from spans (#2969)
Browse files Browse the repository at this point in the history
  • Loading branch information
phacops authored Jan 31, 2024
1 parent ba4a9c3 commit 0f7e3d4
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Emit a usage metric for total spans. ([#3007](https://github.com/getsentry/relay/pull/3007))
- Drop timestamp from metrics partition key. ([#3025](https://github.com/getsentry/relay/pull/3025))
- Drop spans ending outside the valid timestamp range. ([#3013](https://github.com/getsentry/relay/pull/3013))
- Extract INP metrics from spans. ([#2969](https://github.com/getsentry/relay/pull/2969))

## 24.1.1

Expand Down
97 changes: 97 additions & 0 deletions relay-dynamic-config/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ fn span_metrics() -> impl IntoIterator<Item = MetricSpec> {

let is_mobile_sdk = RuleCondition::eq("span.sentry_tags.mobile", "true");

let is_allowed_browser = RuleCondition::eq(
"span.sentry_tags.browser.name",
vec!["Chrome", "Firefox", "Safari", "Edge", "Opera"],
);

// This filter is based on
// https://github.com/getsentry/sentry/blob/e01885215ff1a5b4e0da3046b4d929398a946360/static/app/views/starfish/views/screens/screenLoadSpans/spanOpSelector.tsx#L31-L34
let is_screen = RuleCondition::eq("span.sentry_tags.transaction.op", "ui.load")
Expand Down Expand Up @@ -400,5 +405,97 @@ fn span_metrics() -> impl IntoIterator<Item = MetricSpec> {
.always(), // already guarded by condition on metric
],
},
MetricSpec {
category: DataCategory::Span,
mri: "d:spans/webvital.score.total@ratio".into(),
field: Some("span.measurements.score.total.value".into()),
condition: Some(is_allowed_browser.clone()),
tags: vec![
Tag::with_key("transaction.op")
.from_field("span.sentry_tags.transaction.op")
.always(),
Tag::with_key("transaction")
.from_field("span.sentry_tags.transaction")
.always(),
Tag::with_key("environment")
.from_field("span.sentry_tags.environment")
.always(),
Tag::with_key("release")
.from_field("span.sentry_tags.release")
.always(),
Tag::with_key("browser.name")
.from_field("span.browser.name")
.always(), // already guarded by condition on metric
],
},
MetricSpec {
category: DataCategory::Span,
mri: "d:spans/webvital.score.inp@ratio".into(),
field: Some("span.measurements.score.inp.value".into()),
condition: Some(is_allowed_browser.clone()),
tags: vec![
Tag::with_key("span.op")
.from_field("span.sentry_tags.op")
.always(),
Tag::with_key("transaction")
.from_field("span.sentry_tags.transaction")
.always(),
Tag::with_key("environment")
.from_field("span.sentry_tags.environment")
.always(),
Tag::with_key("release")
.from_field("span.sentry_tags.release")
.always(),
Tag::with_key("browser.name")
.from_field("span.sentry_tags.browser.name")
.always(), // already guarded by condition on metric
],
},
MetricSpec {
category: DataCategory::Span,
mri: "d:spans/webvital.score.weight.inp@ratio".into(),
field: Some("span.measurements.score.weight.inp.value".into()),
condition: Some(is_allowed_browser.clone()),
tags: vec![
Tag::with_key("span.op")
.from_field("span.sentry_tags.op")
.always(),
Tag::with_key("transaction")
.from_field("span.sentry_tags.transaction")
.always(),
Tag::with_key("environment")
.from_field("span.sentry_tags.environment")
.always(),
Tag::with_key("release")
.from_field("span.sentry_tags.release")
.always(),
Tag::with_key("browser.name")
.from_field("span.sentry_tags.browser.name")
.always(), // already guarded by condition on metric
],
},
MetricSpec {
category: DataCategory::Span,
mri: "d:spans/webvital.inp@millisecond".into(),
field: Some("span.measurements.inp.value".into()),
condition: Some(is_allowed_browser),
tags: vec![
Tag::with_key("span.op")
.from_field("span.sentry_tags.op")
.always(),
Tag::with_key("transaction")
.from_field("span.sentry_tags.transaction")
.always(),
Tag::with_key("environment")
.from_field("span.sentry_tags.environment")
.always(),
Tag::with_key("release")
.from_field("span.sentry_tags.release")
.always(),
Tag::with_key("browser.name")
.from_field("span.sentry_tags.browser.name")
.always(), // already guarded by condition on metric
],
},
]
}
8 changes: 4 additions & 4 deletions relay-event-normalization/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,11 +627,11 @@ pub fn normalize_measurements(
}
}

/// Computes performance score measurements.
/// Computes performance score measurements for an event.
///
/// This computes score from vital measurements, using config options to define how it is
/// calculated.
fn normalize_performance_score(
pub fn normalize_performance_score(
event: &mut Event,
performance_score: Option<&PerformanceScoreConfig>,
) {
Expand All @@ -655,8 +655,8 @@ fn normalize_performance_score(
// a measurement with weight is missing.
break;
}
let mut score_total = 0.0;
let mut weight_total = 0.0;
let mut score_total = 0.0f64;
let mut weight_total = 0.0f64;
for component in &profile.score_components {
// Skip optional components if they are not present on the event.
if component.optional
Expand Down
4 changes: 3 additions & 1 deletion relay-event-normalization/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ pub use validation::{
TransactionValidationConfig,
};
pub mod replay;
pub use event::{normalize_event, normalize_measurements, NormalizationConfig};
pub use event::{
normalize_event, normalize_measurements, normalize_performance_score, NormalizationConfig,
};
pub use normalize::breakdowns::*;
pub use normalize::*;
pub use remove_other::RemoveOtherProcessor;
Expand Down
61 changes: 60 additions & 1 deletion relay-event-normalization/src/normalize/span/tag_extraction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use relay_base_schema::metrics::{InformationUnit, MetricUnit};
use relay_event_schema::protocol::{
AppContext, Event, Measurement, OsContext, Span, Timestamp, TraceContext,
AppContext, BrowserContext, Event, Measurement, OsContext, Span, Timestamp, TraceContext,
};
use relay_protocol::{Annotated, Value};
use sqlparser::ast::Visit;
Expand All @@ -32,6 +32,7 @@ pub enum SpanTagKey {
Transaction,
TransactionMethod,
TransactionOp,
BrowserName,
// `"true"` if the transaction was sent by a mobile SDK.
Mobile,
DeviceClass,
Expand Down Expand Up @@ -76,6 +77,7 @@ impl SpanTagKey {
SpanTagKey::TransactionOp => "transaction.op",
SpanTagKey::Mobile => "mobile",
SpanTagKey::DeviceClass => "device.class",
SpanTagKey::BrowserName => "browser.name",

SpanTagKey::Action => "action",
SpanTagKey::Category => "category",
Expand Down Expand Up @@ -228,6 +230,13 @@ pub fn extract_shared_tags(event: &Event) -> BTreeMap<SpanTagKey, String> {
tags.insert(SpanTagKey::DeviceClass, device_class.into());
}

if let Some(browser_name) = event
.context::<BrowserContext>()
.and_then(|v| v.name.value())
{
tags.insert(SpanTagKey::BrowserName, browser_name.into());
}

tags
}

Expand Down Expand Up @@ -1274,4 +1283,54 @@ LIMIT 1
let tags = span.value().unwrap().sentry_tags.value().unwrap();
assert_eq!(tags.get("main_thread"), None);
}

#[test]
fn test_browser_name_and_geo_country_code() {
let json = r#"
{
"type": "transaction",
"platform": "javascript",
"start_timestamp": "2021-04-26T07:59:01+0100",
"timestamp": "2021-04-26T08:00:00+0100",
"transaction": "foo",
"contexts": {
"trace": {
"trace_id": "ff62a8b040f340bda5d830223def1d81",
"span_id": "bd429c44b67a3eb4"
},
"browser": {
"name": "Chrome"
}
},
"spans": [
{
"op": "resource.script",
"span_id": "bd429c44b67a3eb1",
"start_timestamp": 1597976300.0000000,
"timestamp": 1597976302.0000000,
"trace_id": "ff62a8b040f340bda5d830223def1d81"
}
]
}
"#;

let mut event = Annotated::<Event>::from_json(json)
.unwrap()
.into_value()
.unwrap();

extract_span_tags(
&mut event,
&Config {
max_tag_value_size: 200,
},
);

let span = &event.spans.value().unwrap()[0];
let tags = span.value().unwrap().sentry_tags.value().unwrap();
assert_eq!(
tags.get("browser.name"),
Some(&Annotated::new("Chrome".to_string()))
);
}
}
17 changes: 17 additions & 0 deletions relay-event-schema/src/protocol/span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ impl Getter for Span {
val.into()
} else if let Some(key) = path.strip_prefix("sentry_tags.") {
self.sentry_tags.value()?.get(key)?.as_str()?.into()
} else if let Some(rest) = path.strip_prefix("measurements.") {
let name = rest.strip_suffix(".value")?;
self.measurements
.value()?
.get(name)?
.value()?
.value
.value()?
.into()
} else {
return None;
}
Expand Down Expand Up @@ -233,6 +242,9 @@ mod tests {
"data": {
"foo": {"bar": 1},
"foo.bar": 2
},
"measurements": {
"some": {"value": 100.0}
}
}"#,
)
Expand All @@ -246,6 +258,11 @@ mod tests {
assert_eq!(span.get_value("span.data"), None);
assert_eq!(span.get_value("span.data."), None);
assert_eq!(span.get_value("span.data.x"), None);

assert_eq!(
span.get_value("span.measurements.some.value"),
Some(Val::F64(100.0))
);
}

#[test]
Expand Down
36 changes: 36 additions & 0 deletions relay-server/src/metrics_extraction/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,4 +1426,40 @@ mod tests {
assert_eq!(metric.tag("ttfd"), Some("ttfd"));
}
}

#[test]
fn test_extract_span_metrics_performance_score() {
let json = r#"
{
"op": "ui.interaction.click",
"parent_span_id": "8f5a2b8768cafb4e",
"span_id": "bd429c44b67a3eb4",
"start_timestamp": 1597976300.0000000,
"timestamp": 1597976302.0000000,
"exclusive_time": 2000.0,
"trace_id": "ff62a8b040f340bda5d830223def1d81",
"sentry_tags": {
"browser.name": "Chrome",
"op": "ui.interaction.click"
},
"measurements": {
"score.total": {"value": 1.0},
"score.inp": {"value": 1.0},
"score.weight.inp": {"value": 1.0},
"inp": {"value": 1.0}
}
}
"#;
let span = Annotated::from_json(json).unwrap();
let metrics = extract_span_metrics(span.value().unwrap());

for mri in [
"d:spans/webvital.inp@millisecond",
"d:spans/webvital.score.inp@ratio",
"d:spans/webvital.score.total@ratio",
"d:spans/webvital.score.weight.inp@ratio",
] {
assert!(metrics.iter().any(|b| b.name == mri));
}
}
}
Loading

0 comments on commit 0f7e3d4

Please sign in to comment.