diff --git a/.chloggen/tailsampling-record-policy.yaml b/.chloggen/tailsampling-record-policy.yaml new file mode 100644 index 000000000000..06f78576d1d2 --- /dev/null +++ b/.chloggen/tailsampling-record-policy.yaml @@ -0,0 +1,15 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: processor/tailsampling + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: | + Adds support for optionally recording the policy (and any composite policy) associated with an inclusive tail processor sampling decision. + This functionality is disabled by default, you can enable it by passing the following feature flag to the collector: `+processor.tailsamplingprocessor.recordpolicy` + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [35180] \ No newline at end of file diff --git a/processor/tailsamplingprocessor/README.md b/processor/tailsamplingprocessor/README.md index 0c859105c8a8..c9862552334e 100644 --- a/processor/tailsamplingprocessor/README.md +++ b/processor/tailsamplingprocessor/README.md @@ -528,6 +528,16 @@ sum (otelcol_processor_tail_sampling_count_traces_sampled) by (policy) As a reminder, a policy voting to sample the trace does not guarantee sampling; an "inverted not" decision from another policy would still discard the trace. +### Tracking sampling policy +To better understand _which_ sampling policy made the decision to include a trace, you can enable tracking the policy responsible for sampling a trace via the `processor.tailsamplingprocessor.recordpolicy` feature gate. + +When this feature gate is set, this will add additional attributes on each sampled span scope: + +| Attribute | Description | Present? | +|---------------------------------|---------------------------------------------------------------------------|----------------------------| +| `tailsampling.policy` | Records the configured name of the policy that sampled a trace | Always | +| `tailsampling.composite_policy` | Records the configured name of a composite subpolicy that sampled a trace | When composite policy used | + ### Policy Evaluation Errors ``` diff --git a/processor/tailsamplingprocessor/internal/sampling/util.go b/processor/tailsamplingprocessor/internal/sampling/util.go index 9669ea39efbe..d8aedc686d3b 100644 --- a/processor/tailsamplingprocessor/internal/sampling/util.go +++ b/processor/tailsamplingprocessor/internal/sampling/util.go @@ -95,9 +95,10 @@ func invertHasInstrumentationLibrarySpanWithCondition(ilss ptrace.ScopeSpansSlic } func SetAttrOnScopeSpans(data *TraceData, attrName string, attrKey string) { + data.Mutex.Lock() + defer data.Mutex.Unlock() rs := data.ReceivedBatches.ResourceSpans() - for i := 0; i < rs.Len(); i++ { rss := rs.At(i) for j := 0; j < rss.ScopeSpans().Len(); j++ { diff --git a/processor/tailsamplingprocessor/internal/sampling/util_test.go b/processor/tailsamplingprocessor/internal/sampling/util_test.go new file mode 100644 index 000000000000..7e41f1f46d96 --- /dev/null +++ b/processor/tailsamplingprocessor/internal/sampling/util_test.go @@ -0,0 +1,87 @@ +package sampling + +import ( + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" + "testing" +) + +func TestSetAttrOnScopeSpans_Empty(t *testing.T) { + traces := ptrace.NewTraces() + traceData := &TraceData{ + ReceivedBatches: traces, + } + + SetAttrOnScopeSpans(traceData, "test.attr", "value") +} + +func TestSetAttrOnScopeSpans_Many(t *testing.T) { + + assertAttrExists := func(t *testing.T, attrs pcommon.Map, key string, value string) { + v, ok := attrs.Get(key) + assert.True(t, ok) + assert.Equal(t, value, v.AsString()) + } + + traces := ptrace.NewTraces() + + rs1 := traces.ResourceSpans().AppendEmpty() + ss1 := rs1.ScopeSpans().AppendEmpty() + span1 := ss1.Spans().AppendEmpty() + span2 := ss1.Spans().AppendEmpty() + ss2 := rs1.ScopeSpans().AppendEmpty() + span3 := ss2.Spans().AppendEmpty() + rs2 := traces.ResourceSpans().AppendEmpty() + ss3 := rs2.ScopeSpans().AppendEmpty() + span4 := ss3.Spans().AppendEmpty() + + traceData := &TraceData{ + ReceivedBatches: traces, + } + + SetAttrOnScopeSpans(traceData, "test.attr", "value") + + assertAttrExists(t, ss1.Scope().Attributes(), "test.attr", "value") + assertAttrExists(t, ss2.Scope().Attributes(), "test.attr", "value") + assertAttrExists(t, ss3.Scope().Attributes(), "test.attr", "value") + + _, ok := span1.Attributes().Get("test.attr") + assert.False(t, ok) + _, ok = span2.Attributes().Get("test.attr") + assert.False(t, ok) + _, ok = span3.Attributes().Get("test.attr") + assert.False(t, ok) + _, ok = span4.Attributes().Get("test.attr") + assert.False(t, ok) +} + +func BenchmarkSetAttrOnScopeSpans(b *testing.B) { + for n := 0; n < b.N; n++ { + + traces := ptrace.NewTraces() + + for i := 0; i < 5; i++ { + rs := traces.ResourceSpans().AppendEmpty() + ss1 := rs.ScopeSpans().AppendEmpty() + ss1.Spans().AppendEmpty() + ss1.Spans().AppendEmpty() + ss1.Spans().AppendEmpty() + + ss2 := rs.ScopeSpans().AppendEmpty() + ss2.Spans().AppendEmpty() + ss2.Spans().AppendEmpty() + + ss3 := rs.ScopeSpans().AppendEmpty() + ss3.Spans().AppendEmpty() + } + + traceData := &TraceData{ + ReceivedBatches: traces, + } + + b.StartTimer() + SetAttrOnScopeSpans(traceData, "test.attr", "value") + b.StopTimer() + } +}