Skip to content

Commit 9798759

Browse files
authored
Statically define trace observability attributes (#7263)
There are a finite set of attributes that can be recorded with self observability instruments. Cache these combinations to avoid computation and allocations on the hot-path of tracing. ### Benchmarks ```console $ benchstat main_25d02741f.txt cache-trace-obs-sets_05fd6ee.txt goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/sdk/trace cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz │ main_25d02741f.txt │ cache-trace-obs-sets_05fd6ee.txt │ │ sec/op │ sec/op vs base │ SpanEnd/SelfObservabilityEnabled-8 366.2n ± 5% 229.2n ± 3% -37.39% (p=0.000 n=10) TraceStart/SelfObservabilityEnabled-8 1076.0n ± 2% 800.2n ± 2% -25.63% (p=0.000 n=10) geomean 627.7n 428.3n -31.76% │ main_25d02741f.txt │ cache-trace-obs-sets_05fd6ee.txt │ │ B/op │ B/op vs base │ SpanEnd/SelfObservabilityEnabled-8 192.00 ± 0% 64.00 ± 0% -66.67% (p=0.000 n=10) TraceStart/SelfObservabilityEnabled-8 992.0 ± 0% 608.0 ± 0% -38.71% (p=0.000 n=10) geomean 436.4 197.3 -54.80% │ main_25d02741f.txt │ cache-trace-obs-sets_05fd6ee.txt │ │ allocs/op │ allocs/op vs base │ SpanEnd/SelfObservabilityEnabled-8 4.000 ± 0% 2.000 ± 0% -50.00% (p=0.000 n=10) TraceStart/SelfObservabilityEnabled-8 9.000 ± 0% 5.000 ± 0% -44.44% (p=0.000 n=10) geomean 6.000 3.162 -47.30% ```
1 parent be1e57f commit 9798759

File tree

4 files changed

+163
-60
lines changed

4 files changed

+163
-60
lines changed

sdk/trace/span.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"go.opentelemetry.io/otel/sdk/instrumentation"
2222
"go.opentelemetry.io/otel/sdk/resource"
2323
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
24-
"go.opentelemetry.io/otel/semconv/v1.37.0/otelconv"
2524
"go.opentelemetry.io/otel/trace"
2625
"go.opentelemetry.io/otel/trace/embedded"
2726
)
@@ -499,20 +498,11 @@ func (s *recordingSpan) End(options ...trace.SpanEndOption) {
499498

500499
if s.tracer.selfObservabilityEnabled {
501500
defer func() {
502-
// Determine the sampling result and create the corresponding attribute.
503-
var attrSamplingResult attribute.KeyValue
504-
if s.spanContext.IsSampled() {
505-
attrSamplingResult = s.tracer.spanLiveMetric.AttrSpanSamplingResult(
506-
otelconv.SpanSamplingResultRecordAndSample,
507-
)
508-
} else {
509-
attrSamplingResult = s.tracer.spanLiveMetric.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly)
510-
}
511-
512501
// Add the span to the context to ensure the metric is recorded
513502
// with the correct span context.
514503
ctx := trace.ContextWithSpan(context.Background(), s)
515-
s.tracer.spanLiveMetric.Add(ctx, -1, attrSamplingResult)
504+
set := spanLiveSet(s.spanContext.IsSampled())
505+
s.tracer.spanLiveMetric.AddSet(ctx, -1, set)
516506
}()
517507
}
518508

sdk/trace/span_test.go

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -400,18 +400,42 @@ func BenchmarkRecordingSpanSetAttributes(b *testing.B) {
400400
}
401401

402402
func BenchmarkSpanEnd(b *testing.B) {
403-
tracer := NewTracerProvider().Tracer("")
403+
cases := []struct {
404+
name string
405+
env map[string]string
406+
}{
407+
{
408+
name: "Default",
409+
},
410+
{
411+
name: "SelfObservabilityEnabled",
412+
env: map[string]string{
413+
"OTEL_GO_X_SELF_OBSERVABILITY": "True",
414+
},
415+
},
416+
}
417+
404418
ctx := trace.ContextWithSpanContext(context.Background(), trace.SpanContext{})
405419

406-
spans := make([]trace.Span, b.N)
407-
for i := 0; i < b.N; i++ {
408-
_, span := tracer.Start(ctx, "")
409-
spans[i] = span
410-
}
420+
for _, c := range cases {
421+
b.Run(c.name, func(b *testing.B) {
422+
for k, v := range c.env {
423+
b.Setenv(k, v)
424+
}
425+
426+
tracer := NewTracerProvider().Tracer("")
427+
428+
spans := make([]trace.Span, b.N)
429+
for i := 0; i < b.N; i++ {
430+
_, span := tracer.Start(ctx, "")
431+
spans[i] = span
432+
}
411433

412-
b.ReportAllocs()
413-
b.ResetTimer()
414-
for i := 0; i < b.N; i++ {
415-
spans[i].End()
434+
b.ReportAllocs()
435+
b.ResetTimer()
436+
for i := 0; i < b.N; i++ {
437+
spans[i].End()
438+
}
439+
})
416440
}
417441
}

sdk/trace/trace_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2872,7 +2872,6 @@ func TestRecordOnlySampler(t *testing.T) {
28722872
}
28732873

28742874
func BenchmarkTraceStart(b *testing.B) {
2875-
tracer := NewTracerProvider().Tracer("")
28762875
ctx := trace.ContextWithSpanContext(context.Background(), trace.SpanContext{})
28772876

28782877
l1 := trace.Link{SpanContext: trace.SpanContext{}, Attributes: []attribute.KeyValue{}}
@@ -2882,6 +2881,7 @@ func BenchmarkTraceStart(b *testing.B) {
28822881

28832882
for _, tt := range []struct {
28842883
name string
2884+
env map[string]string
28852885
options []trace.SpanStartOption
28862886
}{
28872887
{
@@ -2902,8 +2902,20 @@ func BenchmarkTraceStart(b *testing.B) {
29022902
),
29032903
},
29042904
},
2905+
{
2906+
name: "SelfObservabilityEnabled",
2907+
env: map[string]string{
2908+
"OTEL_GO_X_SELF_OBSERVABILITY": "True",
2909+
},
2910+
},
29052911
} {
29062912
b.Run(tt.name, func(b *testing.B) {
2913+
for k, v := range tt.env {
2914+
b.Setenv(k, v)
2915+
}
2916+
2917+
tracer := NewTracerProvider().Tracer("")
2918+
29072919
spans := make([]trace.Span, b.N)
29082920
b.ReportAllocs()
29092921
b.ResetTimer()

sdk/trace/tracer.go

Lines changed: 114 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -54,32 +54,9 @@ func (tr *tracer) Start(
5454
s := tr.newSpan(ctx, name, &config)
5555
newCtx := trace.ContextWithSpan(ctx, s)
5656
if tr.selfObservabilityEnabled {
57-
// Check if the span has a parent span and set the origin attribute accordingly.
58-
var attrParentOrigin attribute.KeyValue
59-
if psc := trace.SpanContextFromContext(ctx); psc.IsValid() {
60-
if psc.IsRemote() {
61-
attrParentOrigin = tr.spanStartedMetric.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote)
62-
} else {
63-
attrParentOrigin = tr.spanStartedMetric.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal)
64-
}
65-
} else {
66-
attrParentOrigin = tr.spanStartedMetric.AttrSpanParentOrigin(otelconv.SpanParentOriginNone)
67-
}
68-
69-
// Determine the sampling result and create the corresponding attribute.
70-
var attrSamplingResult attribute.KeyValue
71-
switch {
72-
case s.SpanContext().IsSampled() && s.IsRecording():
73-
attrSamplingResult = tr.spanStartedMetric.AttrSpanSamplingResult(
74-
otelconv.SpanSamplingResultRecordAndSample,
75-
)
76-
case s.IsRecording():
77-
attrSamplingResult = tr.spanStartedMetric.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly)
78-
default:
79-
attrSamplingResult = tr.spanStartedMetric.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop)
80-
}
81-
82-
tr.spanStartedMetric.Add(newCtx, 1, attrParentOrigin, attrSamplingResult)
57+
psc := trace.SpanContextFromContext(ctx)
58+
set := spanStartedSet(psc, s)
59+
tr.spanStartedMetric.AddSet(newCtx, 1, set)
8360
}
8461

8562
if rw, ok := s.(ReadWriteSpan); ok && s.IsRecording() {
@@ -192,20 +169,11 @@ func (tr *tracer) newRecordingSpan(
192169
s.SetAttributes(config.Attributes()...)
193170

194171
if tr.selfObservabilityEnabled {
195-
// Determine the sampling result and create the corresponding attribute.
196-
var attrSamplingResult attribute.KeyValue
197-
if s.spanContext.IsSampled() {
198-
attrSamplingResult = tr.spanLiveMetric.AttrSpanSamplingResult(
199-
otelconv.SpanSamplingResultRecordAndSample,
200-
)
201-
} else {
202-
attrSamplingResult = tr.spanLiveMetric.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly)
203-
}
204-
205172
// Propagate any existing values from the context with the new span to
206173
// the measurement context.
207174
ctx = trace.ContextWithSpan(ctx, s)
208-
tr.spanLiveMetric.Add(ctx, 1, attrSamplingResult)
175+
set := spanLiveSet(s.spanContext.IsSampled())
176+
tr.spanLiveMetric.AddSet(ctx, 1, set)
209177
}
210178

211179
return s
@@ -215,3 +183,112 @@ func (tr *tracer) newRecordingSpan(
215183
func (tr *tracer) newNonRecordingSpan(sc trace.SpanContext) nonRecordingSpan {
216184
return nonRecordingSpan{tracer: tr, sc: sc}
217185
}
186+
187+
type parentState int
188+
189+
const (
190+
parentStateNoParent parentState = iota
191+
parentStateLocalParent
192+
parentStateRemoteParent
193+
)
194+
195+
type samplingState int
196+
197+
const (
198+
samplingStateDrop samplingState = iota
199+
samplingStateRecordOnly
200+
samplingStateRecordAndSample
201+
)
202+
203+
type spanStartedSetKey struct {
204+
parent parentState
205+
sampling samplingState
206+
}
207+
208+
var spanStartedSetCache = map[spanStartedSetKey]attribute.Set{
209+
{parentStateNoParent, samplingStateDrop}: attribute.NewSet(
210+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginNone),
211+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop),
212+
),
213+
{parentStateLocalParent, samplingStateDrop}: attribute.NewSet(
214+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal),
215+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop),
216+
),
217+
{parentStateRemoteParent, samplingStateDrop}: attribute.NewSet(
218+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote),
219+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultDrop),
220+
),
221+
222+
{parentStateNoParent, samplingStateRecordOnly}: attribute.NewSet(
223+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginNone),
224+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly),
225+
),
226+
{parentStateLocalParent, samplingStateRecordOnly}: attribute.NewSet(
227+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal),
228+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly),
229+
),
230+
{parentStateRemoteParent, samplingStateRecordOnly}: attribute.NewSet(
231+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote),
232+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordOnly),
233+
),
234+
235+
{parentStateNoParent, samplingStateRecordAndSample}: attribute.NewSet(
236+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginNone),
237+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordAndSample),
238+
),
239+
{parentStateLocalParent, samplingStateRecordAndSample}: attribute.NewSet(
240+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginLocal),
241+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordAndSample),
242+
),
243+
{parentStateRemoteParent, samplingStateRecordAndSample}: attribute.NewSet(
244+
otelconv.SDKSpanStarted{}.AttrSpanParentOrigin(otelconv.SpanParentOriginRemote),
245+
otelconv.SDKSpanStarted{}.AttrSpanSamplingResult(otelconv.SpanSamplingResultRecordAndSample),
246+
),
247+
}
248+
249+
func spanStartedSet(psc trace.SpanContext, span trace.Span) attribute.Set {
250+
key := spanStartedSetKey{
251+
parent: parentStateNoParent,
252+
sampling: samplingStateDrop,
253+
}
254+
255+
if psc.IsValid() {
256+
if psc.IsRemote() {
257+
key.parent = parentStateRemoteParent
258+
} else {
259+
key.parent = parentStateLocalParent
260+
}
261+
}
262+
263+
if span.IsRecording() {
264+
if span.SpanContext().IsSampled() {
265+
key.sampling = samplingStateRecordAndSample
266+
} else {
267+
key.sampling = samplingStateRecordOnly
268+
}
269+
}
270+
271+
return spanStartedSetCache[key]
272+
}
273+
274+
type spanLiveSetKey struct {
275+
sampled bool
276+
}
277+
278+
var spanLiveSetCache = map[spanLiveSetKey]attribute.Set{
279+
{true}: attribute.NewSet(
280+
otelconv.SDKSpanLive{}.AttrSpanSamplingResult(
281+
otelconv.SpanSamplingResultRecordAndSample,
282+
),
283+
),
284+
{false}: attribute.NewSet(
285+
otelconv.SDKSpanLive{}.AttrSpanSamplingResult(
286+
otelconv.SpanSamplingResultRecordOnly,
287+
),
288+
),
289+
}
290+
291+
func spanLiveSet(sampled bool) attribute.Set {
292+
key := spanLiveSetKey{sampled: sampled}
293+
return spanLiveSetCache[key]
294+
}

0 commit comments

Comments
 (0)