-
Notifications
You must be signed in to change notification settings - Fork 93
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(spans): Extract transaction from segment span #3375
Conversation
if has_fields { | ||
let context_key = <$ContextType as DefaultContext>::default_key().into(); | ||
contexts.insert(context_key, ContextInner(context.into_context()).into()); | ||
} |
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 prevents an empty ProfileContext
from appearing in the event.
if trace_context.exclusive_time.value().is_some() { | ||
// Exclusive time already set, respect. | ||
return; | ||
} |
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 is a necessary change in behavior: A transaction derived from a standalone span does not have child spans, so we need to keep the exclusive_time
set on the transaction, otherwise exclusive_time
will always equal the full duration.
/// Returns a shared reference to the reservoir counters. | ||
pub fn counters(&self) -> ReservoirCounters { | ||
self.counters.clone() | ||
} |
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.
Needed to split off a ProcessEnvelope
.
|
||
/// Whether or not spans have been extracted from a transaction. | ||
#[serde(default, skip_serializing_if = "is_false")] | ||
spans_extracted: 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.
With these two flags, we can prevent conversion between events and spans from going in circles.
config: &MetricExtractionConfig, | ||
max_tag_value_size: usize, | ||
) -> Vec<Bucket> { | ||
let mut metrics = generic::extract_metrics(event, config); | ||
|
||
// If spans were already extracted for an event, | ||
// we rely on span processing to extract metrics. | ||
if !spans_extracted { |
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.
Note to self: Double-check if semantics make sense.
e6c71c7
to
800ff44
Compare
Non-functional change to get rid of `#[allow(clippy::too_many_arguments)]` on `EnvelopeProcessorService::new`. This PR was originally part of #3375, which adds yet another `Addr`, but I decided to make a separate PR for reviewability.
@@ -383,6 +384,9 @@ pub fn serialize<G: EventProcessing>( | |||
// If transaction metrics were extracted, set the corresponding item header | |||
event_item.set_metrics_extracted(state.event_metrics_extracted); | |||
|
|||
// TODO: The state should simply maintain & update an `ItemHeaders` object. |
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 will follow up on this in a different PR.
Still requires some more test coverage, but opening for review to get feedback. |
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.
Overall lgtm. Leaving a few comments and questions:
- How are we planning to measure COGS for transactions coming from segments?
- A single message with an envelope with N spans will result in N new messages in the processor, and we'd drop messages if the processor's queue is full. Solving that problem is out of scope of this PR, but what do you think about adding observability for that? This queue can grow quite fast without accepting new requests.
@@ -91,10 +98,18 @@ pub fn process( | |||
return ItemAction::Drop(Outcome::Invalid(DiscardReason::Internal)); | |||
}; | |||
|
|||
if should_extract_transactions && !item.transaction_extracted() { | |||
if let Some(transaction) = convert_to_transaction(&annotated_span) { |
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.
Span and transaction normalization may have different requirements, so I'd extract transactions before normalizing spans (line 91 above).
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.
Good point, refactored now. convert_to_transaction
relies on is_segment
normalization, so I moved that part out of normalization to still run before.
@@ -556,20 +580,46 @@ def test_span_ingestion( | |||
"description": "my 3rd protobuf OTel span", | |||
"duration_ms": 500, | |||
"exclusive_time_ms": 500.0, | |||
"is_segment": True, | |||
"is_segment": False, |
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.
Why is this span no longer a segment?
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 deliberately gave it a parent_span_id
so I could verify that transactions are not extracted from regular spans, only from segment spans.
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.
How are we planning to measure COGS for transactions coming from segments?
The extraction part will be accounted as AppFeature::Spans
, the processing as AppFeature::Transactions
. I think this is fine for now, as both are part of Performance cost.
A single message with an envelope with N spans will result in N new messages in the processor, and we'd drop messages if the processor's queue is full. Solving that problem is out of scope of this PR, but what do you think about adding observability for that? This queue can grow quite fast without accepting new requests.
Good point, I added two metrics now so we can observe the number of spin-off transactions per envelope.
@@ -91,10 +98,18 @@ pub fn process( | |||
return ItemAction::Drop(Outcome::Invalid(DiscardReason::Internal)); | |||
}; | |||
|
|||
if should_extract_transactions && !item.transaction_extracted() { | |||
if let Some(transaction) = convert_to_transaction(&annotated_span) { |
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.
Good point, refactored now. convert_to_transaction
relies on is_segment
normalization, so I moved that part out of normalization to still run before.
@@ -556,20 +580,46 @@ def test_span_ingestion( | |||
"description": "my 3rd protobuf OTel span", | |||
"duration_ms": 500, | |||
"exclusive_time_ms": 500.0, | |||
"is_segment": True, | |||
"is_segment": False, |
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 deliberately gave it a parent_span_id
so I could verify that transactions are not extracted from regular spans, only from segment spans.
relay-server/src/statsd.rs
Outdated
@@ -382,6 +382,9 @@ pub enum RelayTimers { | |||
/// This metric is tagged with: | |||
/// - `type`: The type of the health check, `liveness` or `readiness`. | |||
HealthCheckDuration, | |||
|
|||
/// Measurees how many transactions were created from segment spans in a single envelope. |
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.
/// Measurees how many transactions were created from segment spans in a single envelope. | |
/// Measures how many transactions were created from segment spans in a single envelope. |
relay-server/src/statsd.rs
Outdated
@@ -382,6 +382,9 @@ pub enum RelayTimers { | |||
/// This metric is tagged with: | |||
/// - `type`: The type of the health check, `liveness` or `readiness`. | |||
HealthCheckDuration, | |||
|
|||
/// Measurees how many transactions were created from segment spans in a single envelope. | |||
TransactionsFromSpansPerEnvelope, |
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 we move the variant to RelayHistograms
?
Co-authored-by: David Herberth <david.herberth@sentry.io>
ref: #3278
The current state of Performance is that
We already extract spans from transactions to make span-dependent product features work for "transaction"-SDKs. Now we need to also extract transactions from spans to make transaction-dependent product features work for "span"-SDKs:
Point of conversion
When to actually convert the segment span to transaction? I've listed two options below. At the moment, I believe only Option 1 is feasible because the span processing pipeline does not have all features implemented yet.
Option 1 - At the start of envelope processing [Selected (see Option 1a)]
Pros:
Cons:
Option 1a - At the start of span processing in processing Relays [Selected]
Like Option 1, but only done in processing relays:
Pros:
Cons:
Option 2 - At the end of envelope processing (in processing Relays) [Discarded]
Pros:
Cons:
Prevent duplicate data
We already cross the spans/transactions in two places:
"d:transactions/measurements.score.total@ratio"
.To prevent circular conversion of data, I suggest to introduce two new item headers:
"transaction_extracted"
for span items, which will be checked before converting a span to a transaction. For segment spans extracted from transactions, this flag will betrue
from the start."spans_extracted"
for transaction items, which will be checked before extracting spans or span metrics from a transaction. For transactions extracted from spans, this flag will betrue
from the start.In addition, we will stop extracting
"d:transactions/measurements.score.total@ratio"
from spans.TODO
is_segment
false for some.score.total
to ensure that it is never extracted more than once.