Skip to content
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

Merged
merged 16 commits into from
Jul 13, 2023

Conversation

iambriccardo
Copy link
Member

@iambriccardo iambriccardo commented Jul 7, 2023

This PR adds the new sampled check for the dsc.

The new logic will work as follows:

  • If DSC has no sampled field, tagging will not be performed.
  • If DSC has sampled = true, tagging will be performed and the value will depend on the sampling decision.
  • If DSC has sampled = false, tagging will be performed and the value will be false.

Closes getsentry/sentry#52420

@iambriccardo iambriccardo force-pushed the riccardo/feat/dsc-with-sampled branch 2 times, most recently from 58aa85c to 89a72e7 Compare July 7, 2023 07:23
Comment on lines 3366 to 3424
// 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());

Copy link
Contributor

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 ?

Copy link
Member Author

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?

@iambriccardo iambriccardo force-pushed the riccardo/feat/dsc-with-sampled branch 2 times, most recently from 1f40d8d to 8b8ce6e Compare July 10, 2023 06:19
@iambriccardo iambriccardo requested review from TBS1996 and jan-auer July 10, 2023 13:36
}
}

pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
Copy link
Member

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 and string, then always serialize with the default serializer.
  • If we want to be extra lenient, we could ignore all errors and make them None.

Copy link
Member

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.

Comment on lines 2302 to 2304
// 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.
Copy link
Member

@jan-auer jan-auer Jul 10, 2023

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.

@iambriccardo iambriccardo force-pushed the riccardo/feat/dsc-with-sampled branch from 2af9640 to f676fcf Compare July 11, 2023 09:24
@iambriccardo iambriccardo force-pushed the riccardo/feat/dsc-with-sampled branch from eedb303 to 42e6d94 Compare July 11, 2023 13:30
@iambriccardo iambriccardo marked this pull request as ready for review July 11, 2023 14:12
@iambriccardo iambriccardo requested review from a team and jan-auer July 11, 2023 14:12
@iambriccardo iambriccardo force-pushed the riccardo/feat/dsc-with-sampled branch from 42e6d94 to 7606f9b Compare July 11, 2023 14:13
@iambriccardo iambriccardo force-pushed the riccardo/feat/dsc-with-sampled branch from 7606f9b to 376cb7e Compare July 11, 2023 14:13
}
}

pub fn serialize<S>(value: &Option<bool>, serializer: S) -> Result<S::Ok, S::Error>
Copy link
Member

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.

@@ -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 {
Copy link
Member

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.

}
"#;
let dsc = serde_json::from_str::<DynamicSamplingContext>(json).unwrap();
insta::assert_ron_snapshot!(dsc, @r###"
Copy link
Member

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.

let context = event
.contexts
.get_or_insert_with(Contexts::new)
.get_or_insert_with(TraceContext::default_key(), || Trace(Box::default()));
Copy link
Member

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.

}

#[tokio::test]
async fn test_error_is_not_tagged_if_inputs_are_invalid() {
Copy link
Member

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.

Copy link
Member Author

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

relay-server/src/utils/dynamic_sampling.rs Show resolved Hide resolved
// 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) {
Copy link
Member

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...

Copy link
Member Author

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![])),
Copy link
Member

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,
Copy link
Member

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?

Copy link
Member Author

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.

@iambriccardo iambriccardo merged commit e075e84 into master Jul 13, 2023
@iambriccardo iambriccardo deleted the riccardo/feat/dsc-with-sampled branch July 13, 2023 11:36
malwilley added a commit to getsentry/sentry that referenced this pull request Jul 18, 2023
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement updated error tagging
3 participants