Skip to content

Commit be71b4d

Browse files
perf(inject): don't parse otel tracestate on injection (#98)
# Motivation The otel tracestate API is obviously not optimal. Currently we do a roundtrip of otel tracestate -> String -> dd tracestate. This PR removes one step on injection, and keeps the tracestate as a string, which we copy into the injected tracestate
1 parent df2ad56 commit be71b4d

File tree

4 files changed

+54
-23
lines changed

4 files changed

+54
-23
lines changed

datadog-opentelemetry/src/text_map_propagator.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use std::{collections::HashMap, str::FromStr, sync::Arc};
4+
use std::{collections::HashMap, sync::Arc};
55

66
use dd_trace::{catch_panic, sampling::priority, Config};
77
use opentelemetry::{
@@ -10,7 +10,7 @@ use opentelemetry::{
1010
};
1111

1212
use dd_trace_propagation::{
13-
context::{InjectSpanContext, Sampling, SpanContext, SpanLink, Tracestate},
13+
context::{InjectSpanContext, InjectTraceState, Sampling, SpanContext, SpanLink},
1414
DatadogCompositePropagator,
1515
};
1616

@@ -113,7 +113,7 @@ impl DatadogPropagator {
113113
let tracestate = if *otel_tracestate == opentelemetry::trace::TraceState::NONE {
114114
None
115115
} else {
116-
Tracestate::from_str(&otel_tracestate.header()).ok()
116+
Some(InjectTraceState::from_header(otel_tracestate.header()))
117117
};
118118

119119
let tags = if let Some(propagation_tags) = &mut propagation_data.tags {
@@ -129,7 +129,7 @@ impl DatadogPropagator {
129129
sampling,
130130
origin: propagation_data.origin.as_deref(),
131131
tags,
132-
tracestate: tracestate.as_ref(),
132+
tracestate,
133133
};
134134

135135
self.inner.inject(dd_span_context, &mut injector)

dd-trace-propagation/benches/inject_benchmark.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn span_context_to_inject(c: &mut SpanContext) -> InjectSpanContext<'_> {
2626
origin: c.origin.as_deref(),
2727
tags: &mut c.tags,
2828
is_remote: c.is_remote,
29-
tracestate: c.tracestate.as_ref(),
29+
tracestate: None,
3030
}
3131
}
3232

dd-trace-propagation/src/context.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub struct InjectSpanContext<'a> {
7575
// tags needs to be mutable because we insert the error meta field
7676
pub tags: &'a mut HashMap<String, String>,
7777
pub is_remote: bool,
78-
pub tracestate: Option<&'a Tracestate>,
78+
pub tracestate: Option<InjectTraceState>,
7979
}
8080

8181
#[cfg(test)]
@@ -89,7 +89,17 @@ pub(crate) fn span_context_to_inject(c: &mut SpanContext) -> InjectSpanContext<'
8989
origin: c.origin.as_deref(),
9090
tags: &mut c.tags,
9191
is_remote: c.is_remote,
92-
tracestate: c.tracestate.as_ref(),
92+
tracestate: c.tracestate.as_ref().map(|ts| {
93+
InjectTraceState::from_header(ts.additional_values.as_ref().map_or(
94+
String::new(),
95+
|v| {
96+
v.iter()
97+
.map(|(k, v)| format!("{k}={v}"))
98+
.collect::<Vec<_>>()
99+
.join(",")
100+
},
101+
))
102+
}),
93103
}
94104
}
95105

@@ -105,6 +115,29 @@ pub struct SpanContext {
105115
pub tracestate: Option<Tracestate>,
106116
}
107117

118+
/// A tracestate we grab from the parent span
119+
///
120+
/// Only non-dd keys in the tracestate are injected
121+
pub struct InjectTraceState {
122+
header: String,
123+
}
124+
125+
impl InjectTraceState {
126+
pub fn from_header(header: String) -> Self {
127+
Self { header }
128+
}
129+
130+
pub fn additional_values(&self) -> impl Iterator<Item = &str> {
131+
self.header.split(',').filter(|part| {
132+
let (key, value) = part.split_once('=').unwrap_or((part, ""));
133+
key != "dd"
134+
&& !value.is_empty()
135+
&& Tracestate::valid_key(key)
136+
&& Tracestate::valid_value(value)
137+
})
138+
}
139+
}
140+
108141
#[derive(Clone, Debug, PartialEq)]
109142
pub struct Traceparent {
110143
pub sampling_priority: SamplingPriority,
@@ -171,9 +204,7 @@ impl FromStr for Tracestate {
171204
let mut additional_values = vec![];
172205

173206
for v in ts_v {
174-
let mut parts = v.splitn(2, '=');
175-
let key = parts.next().unwrap_or_default();
176-
let value = parts.next().unwrap_or_default();
207+
let (key, value) = v.split_once('=').unwrap_or(("", ""));
177208

178209
if !Tracestate::valid_key(key) || value.is_empty() || !Tracestate::valid_value(value) {
179210
dd_debug!("Tracestate: invalid key or header value: {v}");

dd-trace-propagation/src/tracecontext.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,10 @@ fn inject_tracestate(context: &InjectSpanContext, carrier: &mut dyn Injector) {
249249
}
250250

251251
// Add additional tracestate values if present
252-
if let Some(ts) = context.tracestate {
253-
if let Some(ref additional) = ts.additional_values {
254-
for (key, value) in additional.iter().take(31) {
255-
tracestate.push_str(TRACESTATE_VALUES_SEPARATOR);
256-
tracestate.push_str(key);
257-
tracestate.push('=');
258-
tracestate.push_str(value);
259-
}
252+
if let Some(ts) = &context.tracestate {
253+
for part in ts.additional_values().take(31) {
254+
tracestate.push_str(TRACESTATE_VALUES_SEPARATOR);
255+
tracestate.push_str(part)
260256
}
261257
}
262258

@@ -509,7 +505,10 @@ pub fn keys() -> &'static [String] {
509505
mod test {
510506
use dd_trace::{configuration::TracePropagationStyle, sampling::priority, Config};
511507

512-
use crate::{context::span_context_to_inject, Propagator};
508+
use crate::{
509+
context::{span_context_to_inject, InjectTraceState},
510+
Propagator,
511+
};
513512

514513
use super::*;
515514

@@ -716,7 +715,6 @@ mod test {
716715

717716
#[test]
718717
fn test_inject_traceparent() {
719-
let ts = Tracestate::from_str("other=bleh,atel=test,dd=s:2;o:foo_bar_;t.dm:-4").unwrap();
720718
let mut context = InjectSpanContext {
721719
trace_id: u128::from_str_radix("1111aaaa2222bbbb3333cccc4444dddd", 16).unwrap(),
722720
span_id: u64::from_str_radix("5555eeee6666ffff", 16).unwrap(),
@@ -730,7 +728,9 @@ mod test {
730728
"abc~!@#$%^&*()_+`-=".to_string(),
731729
)]),
732730
is_remote: false,
733-
tracestate: Some(&ts),
731+
tracestate: Some(InjectTraceState::from_header(
732+
"other=bleh,atel=test,dd=s:2;o:foo_bar_;t.dm:-4".to_owned(),
733+
)),
734734
};
735735

736736
let mut carrier: HashMap<String, String> = HashMap::new();
@@ -791,7 +791,7 @@ mod test {
791791
for index in 0..35 {
792792
tracestate.push(format!("state{index}=value-{index}"));
793793
}
794-
let tracestate = Tracestate::from_str(&tracestate.join(",")).unwrap();
794+
let tracestate = tracestate.join(",");
795795

796796
let mut context = InjectSpanContext {
797797
trace_id: u128::from_str_radix("1111aaaa2222bbbb3333cccc4444dddd", 16).unwrap(),
@@ -803,7 +803,7 @@ mod test {
803803
origin: Some("rum"),
804804
tags: &mut HashMap::from([("_dd.p.foo".to_string(), "abc".to_string())]),
805805
is_remote: false,
806-
tracestate: Some(&tracestate),
806+
tracestate: Some(InjectTraceState::from_header(tracestate)),
807807
};
808808

809809
let mut carrier: HashMap<String, String> = HashMap::new();

0 commit comments

Comments
 (0)