Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

- Produce spans to the items topic. ([#4735](https://github.com/getsentry/relay/pull/4735))
- Take into account more types of tokens when doing AI cost calculation. ([#4840](https://github.com/getsentry/relay/pull/4840))
- Use the `FiniteF64` type for measurements. ([#4828](https://github.com/getsentry/relay/pull/4828))

## 25.6.1

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 56 additions & 39 deletions relay-event-normalization/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use relay_event_schema::protocol::{
SpanStatus, Tags, Timestamp, TraceContext, User, VALID_PLATFORMS,
};
use relay_protocol::{
Annotated, Empty, Error, ErrorKind, FromValue, Getter, Meta, Object, Remark, RemarkType, Value,
Annotated, Empty, Error, ErrorKind, FiniteF64, FromValue, Getter, Meta, Object, Remark,
RemarkType, TryFromFloatError, Value,
};
use smallvec::SmallVec;
use uuid::Uuid;
Expand Down Expand Up @@ -866,10 +867,9 @@ pub fn normalize_measurements(
normalize_mobile_measurements(measurements);
normalize_units(measurements);

let duration_millis = match (start_timestamp, end_timestamp) {
(Some(start), Some(end)) => relay_common::time::chrono_to_positive_millis(end - start),
_ => 0.0,
};
let duration_millis = start_timestamp.zip(end_timestamp).and_then(|(start, end)| {
FiniteF64::new(relay_common::time::chrono_to_positive_millis(end - start))
});

compute_measurements(duration_millis, measurements);
if let Some(measurements_config) = measurements_config {
Expand Down Expand Up @@ -910,8 +910,8 @@ pub fn normalize_performance_score(
// a measurement with weight is missing.
continue;
}
let mut score_total = 0.0f64;
let mut weight_total = 0.0f64;
let mut score_total = FiniteF64::ZERO;
let mut weight_total = FiniteF64::ZERO;
for component in &profile.score_components {
// Skip optional components if they are not present on the event.
if component.optional
Expand All @@ -921,44 +921,55 @@ pub fn normalize_performance_score(
}
weight_total += component.weight;
}
if weight_total.abs() < f64::EPSILON {
if weight_total.abs() < FiniteF64::EPSILON {
// All components are optional or have a weight of `0`. We cannot compute
// component weights, so we bail.
continue;
}
for component in &profile.score_components {
// Optional measurements that are not present are given a weight of 0.
let mut normalized_component_weight = 0.0;
let mut normalized_component_weight = FiniteF64::ZERO;

if let Some(value) = measurements.get_value(component.measurement.as_str()) {
normalized_component_weight = component.weight / weight_total;
normalized_component_weight = component.weight.saturating_div(weight_total);
let cdf = utils::calculate_cdf_score(
value.max(0.0), // Webvitals can't be negative, but we need to clamp in case of bad data.
component.p10,
component.p50,
value.to_f64().max(0.0), // Webvitals can't be negative, but we need to clamp in case of bad data.
component.p10.to_f64(),
component.p50.to_f64(),
);

let cdf = Annotated::try_from(cdf);

measurements.insert(
format!("score.ratio.{}", component.measurement),
Measurement {
value: cdf.into(),
value: cdf.clone(),
unit: (MetricUnit::Fraction(FractionUnit::Ratio)).into(),
}
.into(),
);

let component_score = cdf * normalized_component_weight;
score_total += component_score;
should_add_total = true;
let component_score =
cdf.and_then(|cdf| match cdf * normalized_component_weight {
Some(v) => Annotated::new(v),
None => Annotated::from_error(TryFromFloatError, None),
});

if let Some(component_score) = component_score.value() {
score_total += *component_score;
should_add_total = true;
}

measurements.insert(
format!("score.{}", component.measurement),
Measurement {
value: component_score.into(),
value: component_score,
unit: (MetricUnit::Fraction(FractionUnit::Ratio)).into(),
}
.into(),
);
}

measurements.insert(
format!("score.weight.{}", component.measurement),
Measurement {
Expand Down Expand Up @@ -1042,7 +1053,10 @@ impl MutMeasurements for Span {
/// frames_frozen_rate := measurements.frames_frozen / measurements.frames_total
/// stall_percentage := measurements.stall_total_time / transaction.duration
/// ```
fn compute_measurements(transaction_duration_ms: f64, measurements: &mut Measurements) {
fn compute_measurements(
transaction_duration_ms: Option<FiniteF64>,
measurements: &mut Measurements,
) {
if let Some(frames_total) = measurements.get_value("frames_total") {
if frames_total > 0.0 {
if let Some(frames_frozen) = measurements.get_value("frames_frozen") {
Expand All @@ -1063,22 +1077,25 @@ fn compute_measurements(transaction_duration_ms: f64, measurements: &mut Measure
}

// Get stall_percentage
if transaction_duration_ms > 0.0 {
if let Some(stall_total_time) = measurements
.get("stall_total_time")
.and_then(Annotated::value)
{
if matches!(
stall_total_time.unit.value(),
// Accept milliseconds or None, but not other units
Some(&MetricUnit::Duration(DurationUnit::MilliSecond) | &MetricUnit::None) | None
) {
if let Some(stall_total_time) = stall_total_time.value.0 {
let stall_percentage = Measurement {
value: (stall_total_time / transaction_duration_ms).into(),
unit: (MetricUnit::Fraction(FractionUnit::Ratio)).into(),
};
measurements.insert("stall_percentage".to_owned(), stall_percentage.into());
if let Some(transaction_duration_ms) = transaction_duration_ms {
if transaction_duration_ms > 0.0 {
if let Some(stall_total_time) = measurements
.get("stall_total_time")
.and_then(Annotated::value)
{
if matches!(
stall_total_time.unit.value(),
// Accept milliseconds or None, but not other units
Some(&MetricUnit::Duration(DurationUnit::MilliSecond) | &MetricUnit::None)
| None
) {
if let Some(stall_total_time) = stall_total_time.value.0 {
let stall_percentage = Measurement {
value: (stall_total_time / transaction_duration_ms).into(),
unit: (MetricUnit::Fraction(FractionUnit::Ratio)).into(),
};
measurements.insert("stall_percentage".to_owned(), stall_percentage.into());
}
}
}
}
Expand Down Expand Up @@ -1409,7 +1426,7 @@ fn remove_invalid_measurements(
// Check if this is a builtin measurement:
for builtin_measurement in measurements_config.builtin_measurement_keys() {
if builtin_measurement.name() == name {
let value = measurement.value.value().unwrap_or(&0.0);
let value = measurement.value.value().unwrap_or(&FiniteF64::ZERO);
// Drop negative values if the builtin measurement does not allow them.
if !builtin_measurement.allow_negative() && *value < 0.0 {
meta.add_error(Error::invalid(format!(
Expand Down Expand Up @@ -2051,7 +2068,7 @@ mod tests {
fn test_keeps_valid_measurement() {
let name = "lcp";
let measurement = Measurement {
value: Annotated::new(420.69),
value: Annotated::new(420.69.try_into().unwrap()),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
};

Expand All @@ -2062,7 +2079,7 @@ mod tests {
fn test_drops_too_long_measurement_names() {
let name = "lcpppppppppppppppppppppppppppp";
let measurement = Measurement {
value: Annotated::new(420.69),
value: Annotated::new(420.69.try_into().unwrap()),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
};

Expand All @@ -2073,7 +2090,7 @@ mod tests {
fn test_drops_measurements_with_invalid_characters() {
let name = "i æm frøm nørwåy";
let measurement = Measurement {
value: Annotated::new(420.69),
value: Annotated::new(420.69.try_into().unwrap()),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
};

Expand Down
12 changes: 6 additions & 6 deletions relay-event-normalization/src/normalize/breakdowns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ impl EmitBreakdowns for SpanOperationsConfig {
};

let op_value = Measurement {
value: Annotated::new(relay_common::time::duration_to_millis(op_duration)),
value: Annotated::try_from(relay_common::time::duration_to_millis(op_duration)),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
};

Expand All @@ -148,7 +148,7 @@ impl EmitBreakdowns for SpanOperationsConfig {
}

let total_time_value = Annotated::new(Measurement {
value: Annotated::new(relay_common::time::duration_to_millis(total_time)),
value: Annotated::try_from(relay_common::time::duration_to_millis(total_time)),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
});
breakdown.insert("total.time".to_string(), total_time_value);
Expand Down Expand Up @@ -264,7 +264,7 @@ mod tests {
span_ops_breakdown.insert(
"lcp".to_owned(),
Annotated::new(Measurement {
value: Annotated::new(420.69),
value: Annotated::new(420.69.try_into().unwrap()),
unit: Annotated::empty(),
}),
);
Expand Down Expand Up @@ -366,7 +366,7 @@ mod tests {
"ops.http".to_owned(),
Annotated::new(Measurement {
// 1 hour in milliseconds
value: Annotated::new(3_600_000.0),
value: Annotated::new(3_600_000.0.try_into().unwrap()),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
}),
);
Expand All @@ -375,7 +375,7 @@ mod tests {
"ops.db".to_owned(),
Annotated::new(Measurement {
// 2 hours in milliseconds
value: Annotated::new(7_200_000.0),
value: Annotated::new(7_200_000.0.try_into().unwrap()),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
}),
);
Expand All @@ -384,7 +384,7 @@ mod tests {
"total.time".to_owned(),
Annotated::new(Measurement {
// 4 hours and 10 microseconds in milliseconds
value: Annotated::new(14_400_000.01),
value: Annotated::new(14_400_000.01.try_into().unwrap()),
unit: Annotated::new(MetricUnit::Duration(DurationUnit::MilliSecond)),
}),
);
Expand Down
8 changes: 4 additions & 4 deletions relay-event-normalization/src/normalize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::hash::Hash;
use relay_base_schema::metrics::MetricUnit;
use relay_common::glob2::LazyGlob;
use relay_event_schema::protocol::{Event, VALID_PLATFORMS};
use relay_protocol::RuleCondition;
use relay_protocol::{FiniteF64, RuleCondition};
use serde::{Deserialize, Serialize};

pub mod breakdowns;
Expand Down Expand Up @@ -176,11 +176,11 @@ pub struct PerformanceScoreWeightedComponent {
/// profile will be discarded.
pub measurement: String,
/// Weight [0,1.0] of this component in the performance score
pub weight: f64,
pub weight: FiniteF64,
/// p10 used to define the log-normal for calculation
pub p10: f64,
pub p10: FiniteF64,
/// Median used to define the log-normal for calculation
pub p50: f64,
pub p50: FiniteF64,
/// Whether the measurement is optional. If the measurement is missing, performance score processing
/// may still continue, and the weight will be 0.
#[serde(default)]
Expand Down
2 changes: 1 addition & 1 deletion relay-event-normalization/src/normalize/span/ai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub fn map_ai_measurements_to_data(span: &mut Span) {
if let Some(measurements) = measurements {
if target_field.value().is_none() {
if let Some(value) = measurements.get_value(measurement_key) {
target_field.set_value(Value::F64(value).into());
target_field.set_value(Value::F64(value.to_f64()).into());
}
}
}
Expand Down
20 changes: 11 additions & 9 deletions relay-event-normalization/src/normalize/span/tag_extraction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use relay_event_schema::protocol::{
AppContext, BrowserContext, DeviceContext, Event, GpuContext, Measurement, MonitorContext,
OsContext, ProfileContext, RuntimeContext, SentryTags, Span, Timestamp, TraceContext,
};
use relay_protocol::{Annotated, Empty, Value};
use relay_protocol::{Annotated, Empty, FiniteF64, Value};
use sqlparser::ast::Visit;
use sqlparser::ast::{ObjectName, Visitor};
use url::Url;
Expand Down Expand Up @@ -456,7 +456,7 @@ fn extract_segment_measurements(event: &Event) -> BTreeMap<String, Measurement>
MetricUnit::Information(InformationUnit::Byte),
),
] {
if let Some(value) = value_to_f64(field.value()) {
if let Some(value) = value_to_finite_f64(field.value()) {
measurements.insert(
key.into(),
Measurement {
Expand Down Expand Up @@ -1108,13 +1108,15 @@ pub fn extract_tags(
span_tags
}

fn value_to_f64(val: Option<&Value>) -> Option<f64> {
match val {
fn value_to_finite_f64(val: Option<&Value>) -> Option<FiniteF64> {
let float = match val {
Some(Value::F64(f)) => Some(*f),
Some(Value::I64(i)) => Some(*i as f64),
Some(Value::U64(u)) => Some(*u as f64),
_ => None,
}
};

float.and_then(FiniteF64::new)
}

/// Copies specific numeric values from span data to span measurements.
Expand All @@ -1125,7 +1127,7 @@ pub fn extract_measurements(span: &mut Span, is_mobile: bool) {

if span_op.starts_with("cache.") {
if let Some(data) = span.data.value() {
if let Some(value) = value_to_f64(data.cache_item_size.value()) {
if let Some(value) = value_to_finite_f64(data.cache_item_size.value()) {
let measurements = span.measurements.get_or_insert_with(Default::default);
measurements.insert(
"cache.item_size".to_owned(),
Expand Down Expand Up @@ -1155,7 +1157,7 @@ pub fn extract_measurements(span: &mut Span, is_mobile: bool) {
"http.response_transfer_size",
),
] {
if let Some(value) = value_to_f64(field.value()) {
if let Some(value) = value_to_finite_f64(field.value()) {
let measurements = span.measurements.get_or_insert_with(Default::default);
measurements.insert(
key.into(),
Expand Down Expand Up @@ -1189,7 +1191,7 @@ pub fn extract_measurements(span: &mut Span, is_mobile: bool) {
MetricUnit::Information(InformationUnit::Byte),
),
] {
if let Some(value) = value_to_f64(field.value()) {
if let Some(value) = value_to_finite_f64(field.value()) {
let measurements = span.measurements.get_or_insert_with(Default::default);
measurements.insert(
key.into(),
Expand All @@ -1216,7 +1218,7 @@ pub fn extract_measurements(span: &mut Span, is_mobile: bool) {
MetricUnit::Duration(DurationUnit::Second),
),
] {
if let Some(value) = value_to_f64(field.value()) {
if let Some(value) = value_to_finite_f64(field.value()) {
let measurements = span.measurements.get_or_insert_with(Default::default);
measurements.insert(
key.into(),
Expand Down
Loading
Loading