Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions ddtrace/opentelemetry/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,29 @@ func (c *otelCtxToDDCtx) SpanID() uint64 {
}

func (c *otelCtxToDDCtx) ForeachBaggageItem(_ func(k, v string) bool) {}

// SamplingDecision returns the sampling decision associated with this span context.
// According to the OpenTelemetry specification, the sampling decision is made when the span is started.
// Therefore, not having the Sampled TraceFlag set means that the trace should be dropped.
// - https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/tracer.go#L109
// - https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/tracer.go#L126
func (c *otelCtxToDDCtx) SamplingDecision() uint32 {
if c.oc.IsSampled() {
return 2 // decisionKeep
}
return 1 // decisionDrop
Comment thread
genesor marked this conversation as resolved.
}

// Priority returns the sampling priority associated with this span context.
// According to the OpenTelemetry specification, the sampling decision is made when the span is started.
// Therefore, not having the Sampled TraceFlag set means that the trace should be dropped.
// - https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/tracer.go#L109
// - https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/tracer.go#L126
func (c *otelCtxToDDCtx) Priority() *float64 {
if c.oc.IsSampled() {
p := float64(ext.PriorityAutoKeep)
return &p
}
p := float64(ext.PriorityAutoReject)
Comment thread
genesor marked this conversation as resolved.
return &p
}
87 changes: 87 additions & 0 deletions ddtrace/opentelemetry/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"github.com/DataDog/dd-trace-go/v2/ddtrace/baggage"
"github.com/DataDog/dd-trace-go/v2/ddtrace/ext"
"github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"
"github.com/DataDog/dd-trace-go/v2/internal/log"
"github.com/DataDog/dd-trace-go/v2/internal/telemetry"
Expand Down Expand Up @@ -398,3 +399,89 @@ func TestMergeOtelDDBaggage(t *testing.T) {
assert.Equal("otelValue", value)
})
}

func Test_DDOpenTelemetryTracer(t *testing.T) {
traceID, err := oteltrace.TraceIDFromHex("5b8aa5a2d2c872e8321cf37308d69df1")
assert.NoError(t, err)
spanID, err := oteltrace.SpanIDFromHex("051581bf3cb55c11")
assert.NoError(t, err)
parentSpanCtx := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{
TraceID: traceID,
SpanID: spanID,
})

testCases := []struct {
isSampled bool
ddRate float64
expectedResult bool
}{
{
isSampled: true,
ddRate: 1.0,
expectedResult: true,
},
{
isSampled: false,
ddRate: 1.0,
expectedResult: false,
},
{
isSampled: true,
ddRate: 0,
expectedResult: true,
},
{
isSampled: false,
ddRate: 0,
expectedResult: false,
},
}

for _, tc := range testCases {
t.Run(fmt.Sprintf("OTel %t DD rate %f", tc.isSampled, tc.ddRate), func(t *testing.T) {
ddOTelTracer := NewTracerProvider(
tracer.WithLogStartup(false),
tracer.WithSamplingRules([]tracer.SamplingRule{
{Rate: tc.ddRate}, // This should be applied only when a brand new root span is started and should be ignored for a non-root span
}),
).Tracer("")

parentSpanCtx = parentSpanCtx.WithTraceFlags(parentSpanCtx.TraceFlags().WithSampled(tc.isSampled))

ctx := oteltrace.ContextWithSpanContext(context.Background(), parentSpanCtx)
_, span := ddOTelTracer.Start(ctx, "test")
span.End()

childSpanContext := span.SpanContext()
assert.Equal(t, parentSpanCtx.TraceID(), childSpanContext.TraceID())
assert.Equal(t, tc.expectedResult, childSpanContext.IsSampled(),
"inconsistent sampling decision between OTel and DD")
})
}
}

func Test_otelCtxToDDCtx_SamplingDecision_Priority(t *testing.T) {
parentSpanCtx := oteltrace.NewSpanContext(oteltrace.SpanContextConfig{
TraceID: oteltrace.TraceID{0xAA},
SpanID: oteltrace.SpanID{0x01},
})

// Zero value TraceFlags sampling decision - In an OTel Span the sampling decision is taken at start, this
// means that the sampling decision we will receive will always be filled.
// Zero value means that the trace should be dropped
ctx := &otelCtxToDDCtx{parentSpanCtx}
assert.Equal(t, uint32(1), ctx.SamplingDecision())
assert.EqualValues(t, ext.PriorityAutoReject, *ctx.Priority())

// Set sampling decision to true
parentSpanCtx = parentSpanCtx.WithTraceFlags(parentSpanCtx.TraceFlags().WithSampled(true))
ctx = &otelCtxToDDCtx{parentSpanCtx}
assert.Equal(t, uint32(2), ctx.SamplingDecision())
assert.EqualValues(t, ext.PriorityAutoKeep, *ctx.Priority())

// Set sampling decision to false
parentSpanCtx = parentSpanCtx.WithTraceFlags(parentSpanCtx.TraceFlags().WithSampled(false))
ctx = &otelCtxToDDCtx{parentSpanCtx}
assert.Equal(t, uint32(1), ctx.SamplingDecision())
assert.EqualValues(t, ext.PriorityAutoReject, *ctx.Priority())
}
36 changes: 31 additions & 5 deletions ddtrace/tracer/spancontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,16 @@ type SpanContext struct {
baggageOnly bool // when true, indicates this context only propagates baggage items and should not be used for distributed tracing fields
}

// Private interface for span contexts that can propagate sampling decisions.
type spanContextWithSamplingDecision interface {
SamplingDecision() uint32
Priority() *float64
}

// Private interface for converting v1 span contexts to v2 ones.
type spanContextV1Adapter interface {
SamplingDecision() uint32
spanContextWithSamplingDecision
Origin() string
Priority() *float64
PropagatingTags() map[string]string
Tags() map[string]string
}
Expand All @@ -137,14 +142,35 @@ func FromGenericCtx(c ddtrace.SpanContext) *SpanContext {
sc.baggage[k] = v
return true
})

ctxSpl, ok := c.(spanContextWithSamplingDecision)
if !ok {
return &sc
}

// If the generic context has a sampling decision, set it on the trace
// along with the priority if it exists.
// After setting the sampling decision, lock the trace so the decision is
// respected and avoid re-sampling.
if sDecision := samplingDecision(ctxSpl.SamplingDecision()); sDecision != decisionNone {
sc.trace = newTrace()
sc.trace.samplingDecision = sDecision

if p := ctxSpl.Priority(); p != nil {
sc.setSamplingPriority(int(*p), samplernames.Unknown)
sc.trace.setLocked(true)
}
}

ctx, ok := c.(spanContextV1Adapter)
if !ok {
return &sc
}

sc.origin = ctx.Origin()
sc.trace = newTrace()
sc.trace.priority = ctx.Priority()
sc.trace.samplingDecision = samplingDecision(ctx.SamplingDecision())
if sc.trace == nil {
sc.trace = newTrace()
}
sc.trace.tags = ctx.Tags()
sc.trace.propagatingTags = ctx.PropagatingTags()
return &sc
Expand Down
Loading