-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(dynamic-sampling): Update error tagging by considering new field in DSC #2290
Conversation
58aa85c
to
89a72e7
Compare
relay-server/src/actors/processor.rs
Outdated
// We test with incoming dsc that contains `sampled = false`. | ||
let mut envelope = Envelope::from_request(Some(event_id), request_meta.clone()); | ||
let dsc = DynamicSamplingContext { | ||
trace_id: Uuid::new_v4(), | ||
public_key: ProjectKey::parse("abd0f232775f45feab79864e580d160b").unwrap(), | ||
release: Some("1.1.1".to_string()), | ||
user: Default::default(), | ||
replay_id: None, | ||
environment: None, | ||
transaction: Some("transaction1".into()), | ||
sample_rate: None, | ||
sampled: Some(false), | ||
other: BTreeMap::new(), | ||
}; | ||
envelope.set_dsc(dsc); | ||
envelope.add_item(mocked_error_item()); | ||
let sampling_project_state = project_state_with_single_rule(0.0); | ||
let new_envelope = process_envelope_with_root_project_state( | ||
envelope, | ||
Some(Arc::new(sampling_project_state)), | ||
); | ||
let event = extract_first_event_from_envelope(new_envelope); | ||
let trace_context = event | ||
.contexts | ||
.value() | ||
.unwrap() | ||
.get_context(TraceContext::default_key()) | ||
.unwrap(); | ||
|
||
assert!(matches!(trace_context, Trace(..))); | ||
if let Trace(context) = trace_context { | ||
assert!(!context.sampled.value().unwrap()) | ||
} | ||
|
||
// We test with incoming dsc that doesn't have a transaction set. | ||
let mut envelope = Envelope::from_request(Some(event_id), request_meta.clone()); | ||
let dsc = DynamicSamplingContext { | ||
trace_id: Uuid::new_v4(), | ||
public_key: ProjectKey::parse("abd0f232775f45feab79864e580d160b").unwrap(), | ||
release: Some("1.1.1".to_string()), | ||
user: Default::default(), | ||
replay_id: None, | ||
environment: None, | ||
transaction: None, | ||
sample_rate: None, | ||
sampled: Some(true), | ||
other: BTreeMap::new(), | ||
}; | ||
envelope.set_dsc(dsc); | ||
envelope.add_item(mocked_error_item()); | ||
let sampling_project_state = project_state_with_single_rule(0.0); | ||
let new_envelope = process_envelope_with_root_project_state( | ||
envelope, | ||
Some(Arc::new(sampling_project_state)), | ||
); | ||
let event = extract_first_event_from_envelope(new_envelope); | ||
|
||
assert!(event.contexts.value().is_none()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test is getting very long, what do you think about splitting it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had two ways of proceeding, clustering it by areas or individual tests. Which proposal would you have?
1f40d8d
to
8b8ce6e
Compare
relay-sampling/src/lib.rs
Outdated
} | ||
} | ||
|
||
pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Let's support deserializing from both
bool
andstring
, then always serialize with the default serializer. - If we want to be extra lenient, we could ignore all errors and make them
None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a way to deserialize this without going through Value
by implementing a visitor. I can help with that tomorrow.
As for serialization, I'd still ask to serialize directly as boolean. You can remove fn serialize
entirely and just define deserialize_with
.
relay-server/src/actors/processor.rs
Outdated
// In case the dsc doesn't have a transaction, we don't want to tag it. This can happen | ||
// since in tracing without performance we can have a trace that is not made up of | ||
// transactions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not clear on the reasoning behind this, could you elaborate? It looks like we should primarily look at the sampled
flag from the client.
2af9640
to
f676fcf
Compare
eedb303
to
42e6d94
Compare
42e6d94
to
7606f9b
Compare
7606f9b
to
376cb7e
Compare
relay-sampling/src/lib.rs
Outdated
} | ||
} | ||
|
||
pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a way to deserialize this without going through Value
by implementing a visitor. I can help with that tomorrow.
As for serialization, I'd still ask to serialize directly as boolean. You can remove fn serialize
entirely and just define deserialize_with
.
relay-sampling/src/lib.rs
Outdated
@@ -1287,7 +1330,9 @@ impl DynamicSamplingContext { | |||
|
|||
let contexts = event.contexts.value()?; | |||
let context = contexts.get(TraceContext::default_key())?.value()?; | |||
let Context::Trace(ref trace) = context.0 else { return None }; | |||
let Context::Trace(ref trace) = context.0 else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for causing conflicts through #2296. Once you merge or rebase master, this would be removed.
relay-sampling/src/lib.rs
Outdated
} | ||
"#; | ||
let dsc = serde_json::from_str::<DynamicSamplingContext>(json).unwrap(); | ||
insta::assert_ron_snapshot!(dsc, @r###" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of RON, could you assert JSON here? This should roundtrip cleanly, since we require the upstream to parse this again.
relay-server/src/actors/processor.rs
Outdated
let context = event | ||
.contexts | ||
.get_or_insert_with(Contexts::new) | ||
.get_or_insert_with(TraceContext::default_key(), || Trace(Box::default())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here please check for the upstream diff, since there's a new Contexts::insert_or_default()
and the import on enum variant Trace
has been removed as per style guide.
relay-server/src/actors/processor.rs
Outdated
} | ||
|
||
#[tokio::test] | ||
async fn test_error_is_not_tagged_if_inputs_are_invalid() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should such tests rather be unit tests on one of the util functions? It's great to have a few tests covering the full call hierarchy, though the differences between these tests actually occur within is_trace_fully_sampled
, if I read this correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I didn't refactor them since i wanted to wait for your ok first
// In reality the dynamic sampling logic supports optional root state and dsc but it will | ||
// return keep. In our case having a keep in case of none root state and dsc will be | ||
// a problem, since in reality we can't infer anything without trace metadata. | ||
let sampling_result = if let (Some(root_project_state), Some(dsc)) = (root_project_state, dsc) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This function could become more readable if written with early returns. Even though it is just a minor style change, the cases and control flow discussed in the comment demonstrate that we need to take great care here.
let dsc = dsc?;
let root_project_state = root_project_state?;
// If the sampled field is not set, we prefer to not tag the error since we have no clue on
// whether the head of the trace was kept or dropped on the client side.
// If the head of the trace was dropped on the client we will immediately mark
// the trace as not fully sampled.
if !dsc.sampled? {
return Some(false);
}
let sampling_result = get_sampling_result(...);
// rest here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah much better, I personally don't like the usage of !
and ?
in the same expression but it's a matter of personal taste.
), | ||
// If the head of the trace was dropped on the client we will immediately mark | ||
// the trace as not fully sampled. | ||
false => SamplingResult::Drop(MatchedRuleIds(vec![])), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructs a SamplingResult only to convert it back to a boolean false
later. You could map this directly here, and map in the other branch alone. This would also qualify for another early return.
@@ -101,6 +101,51 @@ pub fn get_sampling_result( | |||
SamplingResult::determine_from_sampling_match(sampling_result) | |||
} | |||
|
|||
pub fn is_trace_fully_sampled( | |||
processing_enabled: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a follow-up PR: The processing_enabled
flag doesn't really belong here, and if we trace it in, then it's just to emit an error warning. Could we look at an alternative way or log this unconditionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would have to really try to lift that out and see how we can implement sampling either without or with a semantically more meaningful value.
Due to the changes in getsentry/relay#2290, `trace.sampled` is being applied more conservatively to events. With the current sorting logic, we may be selecting events FROM before the relay change because they were the only ones with `trace.sampled: true`. Since we only check the last 7 days for the `/helpful` endpoint, we can add this line back in once the relay change is 7 days old.
This PR adds the new
sampled
check for the dsc.The new logic will work as follows:
sampled
field, tagging will not be performed.sampled = true
, tagging will be performed and the value will depend on the sampling decision.sampled = false
, tagging will be performed and the value will befalse
.Closes getsentry/sentry#52420