Skip to content

Commit 927edb4

Browse files
committed
fix(propagation): resolve otel sampling state during injection
1 parent 629892d commit 927edb4

12 files changed

Lines changed: 286 additions & 123 deletions

File tree

‎dd-trace-core/src/main/java/datadog/trace/core/DDSpan.java‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -656,10 +656,7 @@ public DDSpan setSamplingPriority(
656656
context
657657
.getPropagationTags()
658658
.updateOtelTraceState(
659-
getTraceId().toLong(),
660-
sampleRate,
661-
probabilitySamplingResult.booleanValue(),
662-
samplingPriority);
659+
getTraceId().toLong(), sampleRate, probabilitySamplingResult.booleanValue());
663660
}
664661
return this;
665662
}

‎dd-trace-core/src/main/java/datadog/trace/core/propagation/HttpCodec.java‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,8 @@ private <C> void applyTraceContextToFirstContext(
285285
ExtractedContext firstContext,
286286
ExtractedContext traceContext,
287287
ExtractionCache<C> extractionCache) {
288-
firstContext
289-
.getPropagationTags()
290-
.updateW3CTracestateFrom(
291-
traceContext.getPropagationTags(), firstContext.getSamplingPriority());
288+
// Propagate newly extracted W3C tracestate to first valid context
289+
firstContext.getPropagationTags().updateW3CTracestateFrom(traceContext.getPropagationTags());
292290
// Check if parent spans differ to reconcile them
293291
if (firstContext.getSpanId() != traceContext.getSpanId()) {
294292
// Override parent span id with W3C one

‎dd-trace-core/src/main/java/datadog/trace/core/propagation/PropagationTags.java‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public interface Factory {
6868
public abstract void forceKeep(int samplingMechanism);
6969

7070
public abstract void updateOtelTraceState(
71-
long traceIdLowOrderBits, double sampleRate, boolean sampled, int samplingPriority);
71+
long traceIdLowOrderBits, double sampleRate, boolean sampled);
7272

7373
public abstract int getSamplingPriority();
7474

@@ -98,11 +98,10 @@ public abstract void updateOtelTraceState(
9898
*/
9999
public abstract void updateW3CTracestate(String tracestate);
100100

101-
/**
102-
* Enriches these tags with the W3C tracestate from {@code source} and the reconciled sampling
103-
* priority.
104-
*/
105-
public abstract void updateW3CTracestateFrom(PropagationTags source, int samplingPriority);
101+
/** Updates the original W3C tracestate header from {@code source}. */
102+
public void updateW3CTracestateFrom(PropagationTags source) {
103+
updateW3CTracestate(source.getW3CTracestate());
104+
}
106105

107106
/**
108107
* Constructs a header value that includes valid propagated _dd.p.* tags and possibly a new
@@ -120,6 +119,15 @@ public abstract void updateOtelTraceState(
120119
*/
121120
public abstract String headerValue(HeaderType headerType, CharSequence lastParentIdOverride);
122121

122+
/**
123+
* Like {@link #headerValue(HeaderType, CharSequence)}, but resolves W3C sampling state using the
124+
* supplied sampling priority.
125+
*/
126+
public String headerValue(
127+
HeaderType headerType, CharSequence lastParentIdOverride, int samplingPriority) {
128+
return headerValue(headerType, lastParentIdOverride);
129+
}
130+
123131
/**
124132
* Fills a provided tagMap with valid propagated _dd.p.* tags and possibly a new sampling decision
125133
* tags _dd.p.dm (root span only) based on the current state, or sets only an error tag if the

‎dd-trace-core/src/main/java/datadog/trace/core/propagation/W3CHttpCodec.java‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,28 @@ public Injector(Map<String, String> invertedBaggageMapping) {
6565
@Override
6666
public <C> void inject(
6767
final DDSpanContext context, final C carrier, final CarrierSetter<C> setter) {
68-
injectTraceParent(context, carrier, setter);
69-
injectTraceState(context, carrier, setter);
68+
int samplingPriority = context.getSamplingPriority();
69+
injectTraceParent(context, carrier, setter, samplingPriority);
70+
injectTraceState(context, carrier, setter, samplingPriority);
7071
injectBaggage(context, carrier, setter);
7172
}
7273

73-
private <C> void injectTraceParent(DDSpanContext context, C carrier, CarrierSetter<C> setter) {
74+
private <C> void injectTraceParent(
75+
DDSpanContext context, C carrier, CarrierSetter<C> setter, int samplingPriority) {
7476
String traceparent =
75-
W3CTraceParent.from(
76-
context.getTraceId(), context.getSpanId(), context.getSamplingPriority() > 0);
77+
W3CTraceParent.from(context.getTraceId(), context.getSpanId(), samplingPriority > 0);
7778
setter.set(carrier, TRACE_PARENT_KEY, traceparent);
7879
}
7980

80-
private <C> void injectTraceState(DDSpanContext context, C carrier, CarrierSetter<C> setter) {
81+
private <C> void injectTraceState(
82+
DDSpanContext context, C carrier, CarrierSetter<C> setter, int samplingPriority) {
8183
PropagationTags propagationTags = context.getPropagationTags();
8284
// Supply the injecting span's id for the W3C `p:` as a parameter rather than mutating it into
8385
// the (possibly trace-level, shared) tags — keeps transient per-injection identity out of
8486
// shared state, so concurrent sibling injects can't race on it.
8587
String tracestate =
86-
propagationTags.headerValue(W3C, DDSpanId.toHexStringPadded(context.getSpanId()));
88+
propagationTags.headerValue(
89+
W3C, DDSpanId.toHexStringPadded(context.getSpanId()), samplingPriority);
8790
if (tracestate != null && !tracestate.isEmpty()) {
8891
setter.set(carrier, TRACE_STATE_KEY, tracestate);
8992
}

‎dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/OtelTraceState.java‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,10 @@ static OtelTraceState updateProbability(
141141

142142
long threshold = computeThreshold(sampleRate);
143143
long randomValue = computeRandomValue(traceIdLowOrderBits);
144-
if (sampled && randomValue < threshold) {
144+
boolean finalSampled = samplingPriority > 0;
145+
if (finalSampled && randomValue < threshold) {
145146
randomValue = threshold;
146-
} else if (!sampled && randomValue >= threshold) {
147+
} else if (!finalSampled && randomValue >= threshold) {
147148
randomValue = threshold == 0 ? 0 : threshold - 1;
148149
}
149150

@@ -170,6 +171,13 @@ OtelTraceState removeThresholdForLimiterDemotion() {
170171
hasLocallyGeneratedRandomValue());
171172
}
172173

174+
OtelTraceState reconcileSamplingDecision(boolean sampled) {
175+
if (randomValue == NO_VALUE || threshold == NO_VALUE || sampled == (randomValue >= threshold)) {
176+
return this;
177+
}
178+
return removeThresholdForLimiterDemotion();
179+
}
180+
173181
String getValue() {
174182
return value;
175183
}

‎dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsCodec.java‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,20 @@ abstract class PTagsCodec {
2424
protected static final String PROPAGATION_ERROR_INCONSISTENT_TID = "inconsistent_tid ";
2525
protected static final TagKey UPSTREAM_SERVICES_DEPRECATED_TAG = TagKey.from("upstream_services");
2626

27-
static String headerValue(PTagsCodec codec, PTags ptags) {
28-
return headerValue(codec, ptags, null);
29-
}
30-
31-
static String headerValue(PTagsCodec codec, PTags ptags, CharSequence lastParentIdOverride) {
32-
int estimate = codec.estimateHeaderSize(ptags);
27+
static String headerValue(
28+
PTagsCodec codec,
29+
PTags ptags,
30+
CharSequence lastParentIdOverride,
31+
OtelTraceState otelTraceState,
32+
int samplingPriority) {
33+
int estimate = codec.estimateHeaderSize(ptags, otelTraceState);
3334
if (estimate == 0) {
3435
return "";
3536
}
3637

3738
// No encoding validation here because we don't allow arbitrary tag change
3839
StringBuilder sb = new StringBuilder(estimate);
39-
int size = codec.appendPrefix(sb, ptags, lastParentIdOverride);
40+
int size = codec.appendPrefix(sb, ptags, lastParentIdOverride, samplingPriority);
4041
if (!ptags.isPropagationTagsDisabled()) {
4142
if (ptags.getDecisionMakerTagValue() != null) {
4243
size = codec.appendTag(sb, DECISION_MAKER_TAG, ptags.getDecisionMakerTagValue(), size);
@@ -72,7 +73,7 @@ static String headerValue(PTagsCodec codec, PTags ptags, CharSequence lastParent
7273
size = codec.appendTag(sb, tagKey, tagValue, size);
7374
}
7475
}
75-
size = codec.appendSuffix(sb, ptags, size);
76+
size = codec.appendSuffix(sb, ptags, size, otelTraceState);
7677
if (codec.isTooLarge(sb, size)) {
7778
return null;
7879
} else {
@@ -176,6 +177,10 @@ static int calcXDatadogTagsSize(int size, TagKey tagKey, TagValue tagValue) {
176177

177178
protected abstract int estimateHeaderSize(PTags pTags);
178179

180+
protected int estimateHeaderSize(PTags pTags, OtelTraceState otelTraceState) {
181+
return estimateHeaderSize(pTags);
182+
}
183+
179184
protected abstract int appendPrefix(StringBuilder sb, PTags ptags);
180185

181186
/**
@@ -186,10 +191,20 @@ protected int appendPrefix(StringBuilder sb, PTags ptags, CharSequence lastParen
186191
return appendPrefix(sb, ptags);
187192
}
188193

194+
protected int appendPrefix(
195+
StringBuilder sb, PTags ptags, CharSequence lastParentIdOverride, int samplingPriority) {
196+
return appendPrefix(sb, ptags, lastParentIdOverride);
197+
}
198+
189199
protected abstract int appendTag(StringBuilder sb, TagElement key, TagElement value, int size);
190200

191201
protected abstract int appendSuffix(StringBuilder sb, PTags ptags, int size);
192202

203+
protected int appendSuffix(
204+
StringBuilder sb, PTags ptags, int size, OtelTraceState otelTraceState) {
205+
return appendSuffix(sb, ptags, size);
206+
}
207+
193208
protected abstract boolean isTooLarge(StringBuilder sb, int size);
194209

195210
protected abstract boolean isEmpty(StringBuilder sb, int size);

‎dd-trace-core/src/main/java/datadog/trace/core/propagation/ptags/PTagsFactory.java‎

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ static class PTags extends PropagationTags {
113113
private volatile TagValue orgPropagationMarkerTagValue;
114114

115115
private volatile OtelTraceState otelTraceState;
116+
private volatile OtelSamplingDecision otelSamplingDecision;
116117

117118
// Static cache for the most-recently-seen rate → TagValue. In steady state a service uses one
118119
// rate, so this eliminates the char[] + String allocation on every new PTags instance.
@@ -267,17 +268,15 @@ private void doUpdateTraceSamplingPriority(int samplingPriority, int samplingMec
267268
}
268269
decisionMakerTagValue = null;
269270
}
270-
if (removeOtelProbability && otelTraceState != null) {
271-
setOtelTraceState(otelTraceState.removeForNonProbabilityDecision());
271+
if (removeOtelProbability) {
272+
setOtelSamplingDecision(OtelSamplingDecision.NON_PROBABILITY);
272273
}
273274
}
274275

275276
@Override
276-
public void updateOtelTraceState(
277-
long traceIdLowOrderBits, double sampleRate, boolean sampled, int samplingPriority) {
278-
setOtelTraceState(
279-
OtelTraceState.updateProbability(
280-
otelTraceState, traceIdLowOrderBits, sampleRate, sampled, samplingPriority));
277+
public void updateOtelTraceState(long traceIdLowOrderBits, double sampleRate, boolean sampled) {
278+
setOtelSamplingDecision(
279+
OtelSamplingDecision.probability(traceIdLowOrderBits, sampleRate, sampled));
281280
}
282281

283282
@Override
@@ -442,7 +441,13 @@ public CharSequence getLastParentId() {
442441
public String headerValue(HeaderType headerType) {
443442
String header = getCachedHeader(headerType);
444443
if (header == null) {
445-
header = PTagsCodec.headerValue(factory.getDecoderEncoder(headerType), this);
444+
header =
445+
PTagsCodec.headerValue(
446+
factory.getDecoderEncoder(headerType),
447+
this,
448+
null,
449+
resolveOtelTraceState(headerType, samplingPriority),
450+
samplingPriority);
446451
if (header != null) {
447452
setCachedHeader(headerType, header);
448453
} else {
@@ -464,7 +469,28 @@ public String headerValue(HeaderType headerType, CharSequence lastParentIdOverri
464469
// Inject-time path: encode fresh with the override; do NOT cache — the W3C `p:` is
465470
// per-injecting-span and these tags may be shared across sibling spans.
466471
String header =
467-
PTagsCodec.headerValue(factory.getDecoderEncoder(headerType), this, lastParentIdOverride);
472+
PTagsCodec.headerValue(
473+
factory.getDecoderEncoder(headerType),
474+
this,
475+
lastParentIdOverride,
476+
resolveOtelTraceState(headerType, samplingPriority),
477+
samplingPriority);
478+
return (header == null || header.isEmpty()) ? null : header;
479+
}
480+
481+
@Override
482+
public String headerValue(
483+
HeaderType headerType, CharSequence lastParentIdOverride, int samplingPriority) {
484+
if (lastParentIdOverride == null && samplingPriority == this.samplingPriority) {
485+
return headerValue(headerType);
486+
}
487+
String header =
488+
PTagsCodec.headerValue(
489+
factory.getDecoderEncoder(headerType),
490+
this,
491+
lastParentIdOverride,
492+
resolveOtelTraceState(headerType, samplingPriority),
493+
samplingPriority);
468494
return (header == null || header.isEmpty()) ? null : header;
469495
}
470496

@@ -560,16 +586,13 @@ public void updateW3CTracestate(String tracestate) {
560586
}
561587

562588
@Override
563-
public void updateW3CTracestateFrom(PropagationTags source, int samplingPriority) {
564-
String sourceTracestate = source.getW3CTracestate();
565-
OtelTraceState sourceOtelTraceState =
566-
source instanceof PTags
567-
? ((PTags) source).getOtelTraceState()
568-
: W3CPTagsCodec.extractOtelTraceState(sourceTracestate);
569-
setW3CTracestate(sourceTracestate, sourceOtelTraceState);
570-
if (samplingPriority != PrioritySampling.UNSET) {
571-
this.samplingPriority = samplingPriority;
589+
public void updateW3CTracestateFrom(PropagationTags source) {
590+
if (!(source instanceof PTags)) {
591+
super.updateW3CTracestateFrom(source);
592+
return;
572593
}
594+
PTags sourcePTags = (PTags) source;
595+
setW3CTracestate(sourcePTags.tracestate, sourcePTags.getOtelTraceState());
573596
}
574597

575598
private void setW3CTracestate(String tracestate, OtelTraceState otelTraceState) {
@@ -589,6 +612,26 @@ void setOtelTraceState(OtelTraceState otelTraceState) {
589612
}
590613
}
591614

615+
private OtelTraceState resolveOtelTraceState(HeaderType headerType, int samplingPriority) {
616+
OtelTraceState current = otelTraceState;
617+
if (headerType != W3C) {
618+
return current;
619+
}
620+
OtelSamplingDecision decision = otelSamplingDecision;
621+
OtelTraceState resolved =
622+
decision == null ? current : decision.resolve(current, samplingPriority);
623+
return resolved == null || samplingPriority == PrioritySampling.UNSET
624+
? resolved
625+
: resolved.reconcileSamplingDecision(samplingPriority > 0);
626+
}
627+
628+
private void setOtelSamplingDecision(OtelSamplingDecision decision) {
629+
if (otelSamplingDecision != decision) {
630+
otelSamplingDecision = decision;
631+
clearCachedHeader(W3C);
632+
}
633+
}
634+
592635
String getError() {
593636
return this.error;
594637
}
@@ -618,5 +661,62 @@ private static boolean isProbabilitySamplingMechanism(int samplingMechanism) {
618661
return false;
619662
}
620663
}
664+
665+
private static final class OtelSamplingDecision {
666+
private static final OtelSamplingDecision NON_PROBABILITY =
667+
new OtelSamplingDecision(0, 0, false, false);
668+
669+
private final long traceIdLowOrderBits;
670+
private final double sampleRate;
671+
private final boolean sampled;
672+
private final boolean probability;
673+
private volatile Resolution resolution;
674+
675+
private OtelSamplingDecision(
676+
long traceIdLowOrderBits, double sampleRate, boolean sampled, boolean probability) {
677+
this.traceIdLowOrderBits = traceIdLowOrderBits;
678+
this.sampleRate = sampleRate;
679+
this.sampled = sampled;
680+
this.probability = probability;
681+
}
682+
683+
private static OtelSamplingDecision probability(
684+
long traceIdLowOrderBits, double sampleRate, boolean sampled) {
685+
return new OtelSamplingDecision(traceIdLowOrderBits, sampleRate, sampled, true);
686+
}
687+
688+
private OtelTraceState resolve(OtelTraceState current, int samplingPriority) {
689+
Resolution cached = resolution;
690+
if (cached != null
691+
&& cached.source == current
692+
&& cached.samplingPriority == samplingPriority) {
693+
return cached.resolved;
694+
}
695+
OtelTraceState resolved;
696+
if (!probability) {
697+
resolved = current == null ? null : current.removeForNonProbabilityDecision();
698+
} else if (current != null) {
699+
resolved = current;
700+
} else {
701+
resolved =
702+
OtelTraceState.updateProbability(
703+
null, traceIdLowOrderBits, sampleRate, sampled, samplingPriority);
704+
}
705+
resolution = new Resolution(current, samplingPriority, resolved);
706+
return resolved;
707+
}
708+
709+
private static final class Resolution {
710+
private final OtelTraceState source;
711+
private final int samplingPriority;
712+
private final OtelTraceState resolved;
713+
714+
private Resolution(OtelTraceState source, int samplingPriority, OtelTraceState resolved) {
715+
this.source = source;
716+
this.samplingPriority = samplingPriority;
717+
this.resolved = resolved;
718+
}
719+
}
720+
}
621721
}
622722
}

0 commit comments

Comments
 (0)