1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-09-16 09:26:25 +02:00

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)
```
This commit is contained in:
Tyler Yahn
2025-08-18 10:27:17 -07:00
committed by GitHub
parent 907d93b8e1
commit 44a0d621ee
2 changed files with 42 additions and 5 deletions

View File

@@ -96,6 +96,17 @@ type Exporter struct {
operationDurationMetric otelconv.SDKExporterOperationDuration
}
var measureAttrsPool = sync.Pool{
New: func() any {
// "component.name" + "component.type" + "error.type"
const n = 1 + 1 + 1
s := make([]attribute.KeyValue, 0, n)
// Return a pointer to a slice instead of a slice itself
// to avoid allocations on every call.
return &s
},
}
// ExportSpans writes spans in json format to stdout.
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
var success int64
@@ -116,11 +127,14 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan)
if err != nil {
// additional attributes for self-observability,
// only spanExportedMetric and operationDurationMetric are supported.
//
// TODO: use a pool to amortize allocations.
attr = make([]attribute.KeyValue, len(e.selfObservabilityAttrs), len(e.selfObservabilityAttrs)+1)
copy(attr, e.selfObservabilityAttrs)
attr = append(attr, semconv.ErrorType(err))
attrs := measureAttrsPool.Get().(*[]attribute.KeyValue)
defer func() {
*attrs = (*attrs)[:0] // reset the slice for reuse
measureAttrsPool.Put(attrs)
}()
*attrs = append(*attrs, e.selfObservabilityAttrs...)
*attrs = append(*attrs, semconv.ErrorType(err))
attr = *attrs
e.spanExportedMetric.Add(ctx, count-success, attr...)
}

View File

@@ -667,3 +667,26 @@ func TestSelfObservabilityInstrumentErrors(t *testing.T) {
assert.ErrorContains(t, err, "span exported metric")
assert.ErrorContains(t, err, "operation duration metric")
}
func BenchmarkExporterExportSpans(b *testing.B) {
b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true")
ss := tracetest.SpanStubs{
{Name: "/foo"},
{
Name: "JSON encoder cannot marshal math.Inf(1)",
Attributes: []attribute.KeyValue{attribute.Float64("", math.Inf(1))},
},
{Name: "/bar"},
}.Snapshots()
ex, err := stdouttrace.New(stdouttrace.WithWriter(io.Discard))
if err != nil {
b.Fatalf("failed to create exporter: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = ex.ExportSpans(context.Background(), ss)
}
_ = err
}