Skip to content

Commit b5999ed

Browse files
committed
Pool attribute slices in stdouttrace self-observability (#7201)
### Benchmarks ```terminal goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/exporters/stdout/stdouttrace cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz │ main.txt │ stdouttrace-pool-obs-attrs.txt │ │ sec/op │ sec/op vs base │ ExporterExportSpans-8 30.55µ ± 2% 31.76µ ± 3% +3.96% (p=0.000 n=10) │ main.txt │ stdouttrace-pool-obs-attrs.txt │ │ B/op │ B/op vs base │ ExporterExportSpans-8 6.538Ki ± 0% 6.353Ki ± 0% -2.84% (p=0.000 n=10) │ main.txt │ stdouttrace-pool-obs-attrs.txt │ │ allocs/op │ allocs/op vs base │ ExporterExportSpans-8 135.0 ± 0% 134.0 ± 0% -0.74% (p=0.000 n=10) ```
1 parent 58fba76 commit b5999ed

File tree

2 files changed

+42
-5
lines changed

2 files changed

+42
-5
lines changed

exporters/stdout/stdouttrace/trace.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ type Exporter struct {
9696
operationDurationMetric otelconv.SDKExporterOperationDuration
9797
}
9898

99+
var measureAttrsPool = sync.Pool{
100+
New: func() any {
101+
// "component.name" + "component.type" + "error.type"
102+
const n = 1 + 1 + 1
103+
s := make([]attribute.KeyValue, 0, n)
104+
// Return a pointer to a slice instead of a slice itself
105+
// to avoid allocations on every call.
106+
return &s
107+
},
108+
}
109+
99110
// ExportSpans writes spans in json format to stdout.
100111
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
101112
var success int64
@@ -116,11 +127,14 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
116127
if err != nil {
117128
// additional attributes for self-observability,
118129
// only spanExportedMetric and operationDurationMetric are supported.
119-
//
120-
// TODO: use a pool to amortize allocations.
121-
attr = make([]attribute.KeyValue, len(e.selfObservabilityAttrs), len(e.selfObservabilityAttrs)+1)
122-
copy(attr, e.selfObservabilityAttrs)
123-
attr = append(attr, semconv.ErrorType(err))
130+
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
131+
defer func() {
132+
*attrs = (*attrs)[:0] // reset the slice for reuse
133+
measureAttrsPool.Put(attrs)
134+
}()
135+
*attrs = append(*attrs, e.selfObservabilityAttrs...)
136+
*attrs = append(*attrs, semconv.ErrorType(err))
137+
attr = *attrs
124138

125139
e.spanExportedMetric.Add(ctx, count-success, attr...)
126140
}

exporters/stdout/stdouttrace/trace_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,3 +667,26 @@ func TestSelfObservabilityInstrumentErrors(t *testing.T) {
667667
assert.ErrorContains(t, err, "span exported metric")
668668
assert.ErrorContains(t, err, "operation duration metric")
669669
}
670+
671+
func BenchmarkExporterExportSpans(b *testing.B) {
672+
b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
673+
ss := tracetest.SpanStubs{
674+
{Name: "/foo"},
675+
{
676+
Name: "JSON encoder cannot marshal math.Inf(1)",
677+
Attributes: []attribute.KeyValue{attribute.Float64("", math.Inf(1))},
678+
},
679+
{Name: "/bar"},
680+
}.Snapshots()
681+
ex, err := stdouttrace.New(stdouttrace.WithWriter(io.Discard))
682+
if err != nil {
683+
b.Fatalf("failed to create exporter: %v", err)
684+
}
685+
686+
b.ReportAllocs()
687+
b.ResetTimer()
688+
for i := 0; i < b.N; i++ {
689+
err = ex.ExportSpans(context.Background(), ss)
690+
}
691+
_ = err
692+
}

0 commit comments

Comments
 (0)