diff --git a/.codespellignore b/.codespellignore index 6bf3abc41e7..2b53a25e1e1 100644 --- a/.codespellignore +++ b/.codespellignore @@ -7,3 +7,4 @@ ans nam valu thirdparty +addOpt diff --git a/exporters/stdout/stdouttrace/trace.go b/exporters/stdout/stdouttrace/trace.go index 142e29e2848..ab31babde9f 100644 --- a/exporters/stdout/stdouttrace/trace.go +++ b/exporters/stdout/stdouttrace/trace.go @@ -55,6 +55,8 @@ func New(options ...Option) (*Exporter, error) { semconv.OTelComponentName(fmt.Sprintf("%s/%d", otelComponentType, counter.NextExporterID())), semconv.OTelComponentTypeKey.String(otelComponentType), } + s := attribute.NewSet(exporter.selfObservabilityAttrs...) + exporter.selfObservabilitySetOpt = metric.WithAttributeSet(s) mp := otel.GetMeterProvider() m := mp.Meter( @@ -91,21 +93,40 @@ type Exporter struct { selfObservabilityEnabled bool selfObservabilityAttrs []attribute.KeyValue // selfObservability common attributes + selfObservabilitySetOpt metric.MeasurementOption spanInflightMetric otelconv.SDKExporterSpanInflight spanExportedMetric otelconv.SDKExporterSpanExported 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 - }, -} +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 + }, + } + + addOptPool = &sync.Pool{ + New: func() any { + const n = 1 // WithAttributeSet + o := make([]metric.AddOption, 0, n) + return &o + }, + } + + recordOptPool = &sync.Pool{ + New: func() any { + const n = 1 // WithAttributeSet + o := make([]metric.RecordOption, 0, n) + return &o + }, + } +) // ExportSpans writes spans in json format to stdout. func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) { @@ -113,17 +134,25 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) if e.selfObservabilityEnabled { count := int64(len(spans)) - e.spanInflightMetric.Add(ctx, count, e.selfObservabilityAttrs...) + addOpt := addOptPool.Get().(*[]metric.AddOption) + defer func() { + *addOpt = (*addOpt)[:0] + addOptPool.Put(addOpt) + }() + + *addOpt = append(*addOpt, e.selfObservabilitySetOpt) + + e.spanInflightMetric.Inst().Add(ctx, count, *addOpt...) defer func(starting time.Time) { - e.spanInflightMetric.Add(ctx, -count, e.selfObservabilityAttrs...) + e.spanInflightMetric.Inst().Add(ctx, -count, *addOpt...) // Record the success and duration of the operation. // // Do not exclude 0 values, as they are valid and indicate no spans // were exported which is meaningful for certain aggregations. - e.spanExportedMetric.Add(ctx, success, e.selfObservabilityAttrs...) + e.spanExportedMetric.Inst().Add(ctx, success, *addOpt...) - attr := e.selfObservabilityAttrs + mOpt := e.selfObservabilitySetOpt if err != nil { // additional attributes for self-observability, // only spanExportedMetric and operationDurationMetric are supported. @@ -134,12 +163,34 @@ func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) }() *attrs = append(*attrs, e.selfObservabilityAttrs...) *attrs = append(*attrs, semconv.ErrorType(err)) - attr = *attrs - e.spanExportedMetric.Add(ctx, count-success, attr...) + // Do not inefficiently make a copy of attrs by using + // WithAttributes instead of WithAttributeSet. + set := attribute.NewSet(*attrs...) + mOpt = metric.WithAttributeSet(set) + + // Reset addOpt with new attribute set. + *addOpt = append((*addOpt)[:0], mOpt) + + e.spanExportedMetric.Inst().Add( + ctx, + count-success, + *addOpt..., + ) } - e.operationDurationMetric.Record(ctx, time.Since(starting).Seconds(), attr...) + recordOpt := recordOptPool.Get().(*[]metric.RecordOption) + defer func() { + *recordOpt = (*recordOpt)[:0] + recordOptPool.Put(recordOpt) + }() + + *recordOpt = append(*recordOpt, mOpt) + e.operationDurationMetric.Inst().Record( + ctx, + time.Since(starting).Seconds(), + *recordOpt..., + ) }(time.Now()) } diff --git a/exporters/stdout/stdouttrace/trace_test.go b/exporters/stdout/stdouttrace/trace_test.go index 02b145aa35a..3706037fed5 100644 --- a/exporters/stdout/stdouttrace/trace_test.go +++ b/exporters/stdout/stdouttrace/trace_test.go @@ -669,7 +669,6 @@ func TestSelfObservabilityInstrumentErrors(t *testing.T) { } func BenchmarkExporterExportSpans(b *testing.B) { - b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") ss := tracetest.SpanStubs{ {Name: "/foo"}, { @@ -678,15 +677,25 @@ func BenchmarkExporterExportSpans(b *testing.B) { }, {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) + run := func(b *testing.B) { + 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 } - _ = err + + b.Run("SelfObservability", func(b *testing.B) { + b.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", "true") + run(b) + }) + + b.Run("NoObservability", run) }