From f7c180ec8f7a330d3b5384cde7b96a051a920d88 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 9 Aug 2025 19:15:48 +0530 Subject: [PATCH 01/11] implementation of `otel.sdk.processor.span.processed` metric for simple_span_processor.go --- CHANGELOG.md | 4 +-- sdk/trace/internal/x/README.md | 1 + sdk/trace/provider.go | 1 + sdk/trace/simple_span_processor.go | 54 +++++++++++++++++++++++++++++- 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8d1a4f91ef..4a59e7595c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,8 +46,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `go.opentelemetry.io/otel/semconv/v1.36.0` package. The package contains semantic conventions from the `v1.36.0` version of the OpenTelemetry Semantic Conventions. See the [migration documentation](./semconv/v1.36.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.34.0.`(#7032) -- Add experimental self-observability span metrics in `go.opentelemetry.io/otel/sdk/trace`. - Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027) +- Add experimental self-observability span and simple span processor metrics in `go.opentelemetry.io/otel/sdk/trace`. + Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #7158) - Add native histogram exemplar support in `go.opentelemetry.io/otel/exporters/prometheus`. (#6772) - Add experimental self-observability log metrics in `go.opentelemetry.io/otel/sdk/log`. Check the `go.opentelemetry.io/otel/sdk/log/internal/x` package documentation for more information. (#7121) diff --git a/sdk/trace/internal/x/README.md b/sdk/trace/internal/x/README.md index 4c79e80a7ce..2a4e100c937 100644 --- a/sdk/trace/internal/x/README.md +++ b/sdk/trace/internal/x/README.md @@ -20,6 +20,7 @@ When enabled, the SDK will create the following metrics using the global `MeterP - `otel.sdk.span.live` - `otel.sdk.span.started` +- `otel.sdk.processor.span.processed` (only for simple span processor) Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics. diff --git a/sdk/trace/provider.go b/sdk/trace/provider.go index fd942d23e80..62e87fd631e 100644 --- a/sdk/trace/provider.go +++ b/sdk/trace/provider.go @@ -20,6 +20,7 @@ import ( const ( defaultTracerName = "go.opentelemetry.io/otel/sdk/tracer" + selfObsScopeName = "go.opentelemetry.io/otel/sdk/trace" ) // tracerProviderConfig. diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 411d9ccdd78..5288854b7ab 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -5,10 +5,18 @@ package trace // import "go.opentelemetry.io/otel/sdk/trace" import ( "context" + "fmt" "sync" + "sync/atomic" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/sdk" + "go.opentelemetry.io/otel/sdk/trace/internal/x" + semconv "go.opentelemetry.io/otel/semconv/v1.36.0" + "go.opentelemetry.io/otel/semconv/v1.36.0/otelconv" ) // simpleSpanProcessor is a SpanProcessor that synchronously sends all @@ -17,6 +25,10 @@ type simpleSpanProcessor struct { exporterMu sync.Mutex exporter SpanExporter stopOnce sync.Once + + selfObservabilityEnabled bool + componentNameAttr attribute.KeyValue + spansProcessedCounter otelconv.SDKProcessorSpanProcessed } var _ SpanProcessor = (*simpleSpanProcessor)(nil) @@ -33,11 +45,42 @@ func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor { ssp := &simpleSpanProcessor{ exporter: exporter, } + ssp.configureSelfObservability() + global.Warn("SimpleSpanProcessor is not recommended for production use, consider using BatchSpanProcessor instead.") return ssp } +var processorIDCounter atomic.Int64 + +// nextProcessorID returns an identifier for this simple span processor, +// starting with 0 and incrementing by 1 each time it is called. +func nextProcessorID() int64 { + return processorIDCounter.Add(1) - 1 +} + +// configureSelfObservability configures metrics for the simple span processor. +func (ssp *simpleSpanProcessor) configureSelfObservability() { + if !x.SelfObservability.Enabled() { + return + } + ssp.selfObservabilityEnabled = true + ssp.componentNameAttr = semconv.OTelComponentName( + fmt.Sprintf("%s/%d", otelconv.ComponentTypeBatchingSpanProcessor, nextProcessorID())) + meter := otel.GetMeterProvider().Meter( + selfObsScopeName, + metric.WithInstrumentationVersion(sdk.Version()), + metric.WithSchemaURL(semconv.SchemaURL), + ) + + var err error + ssp.spansProcessedCounter, err = otelconv.NewSDKProcessorSpanProcessed(meter) + if err != nil { + otel.Handle(err) + } +} + // OnStart does nothing. func (*simpleSpanProcessor) OnStart(context.Context, ReadWriteSpan) {} @@ -47,8 +90,17 @@ func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) { defer ssp.exporterMu.Unlock() if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() { - if err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}); err != nil { + attrs := []attribute.KeyValue{ + ssp.componentNameAttr, + ssp.spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeSimpleSpanProcessor), + } + err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}) + if err != nil { otel.Handle(err) + attrs = append(attrs, semconv.ErrorType(err)) + } + if ssp.selfObservabilityEnabled { + ssp.spansProcessedCounter.Add(context.Background(), 1, attrs...) } } } From f15ae4e233b06cfd953f726fe6f90355f06dc6b5 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 9 Aug 2025 19:26:11 +0530 Subject: [PATCH 02/11] add correct PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a59e7595c5..6fe9cf287a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm The package contains semantic conventions from the `v1.36.0` version of the OpenTelemetry Semantic Conventions. See the [migration documentation](./semconv/v1.36.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.34.0.`(#7032) - Add experimental self-observability span and simple span processor metrics in `go.opentelemetry.io/otel/sdk/trace`. - Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #7158) + Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #7162) - Add native histogram exemplar support in `go.opentelemetry.io/otel/exporters/prometheus`. (#6772) - Add experimental self-observability log metrics in `go.opentelemetry.io/otel/sdk/log`. Check the `go.opentelemetry.io/otel/sdk/log/internal/x` package documentation for more information. (#7121) From d743f6a8ee6eb53d0b94838d6107253eca14e889 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 9 Aug 2025 19:46:40 +0530 Subject: [PATCH 03/11] fix component type --- sdk/trace/simple_span_processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 5288854b7ab..0b53812e587 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -67,7 +67,7 @@ func (ssp *simpleSpanProcessor) configureSelfObservability() { } ssp.selfObservabilityEnabled = true ssp.componentNameAttr = semconv.OTelComponentName( - fmt.Sprintf("%s/%d", otelconv.ComponentTypeBatchingSpanProcessor, nextProcessorID())) + fmt.Sprintf("%s/%d", otelconv.ComponentTypeSimpleSpanProcessor, nextProcessorID())) meter := otel.GetMeterProvider().Meter( selfObsScopeName, metric.WithInstrumentationVersion(sdk.Version()), From 17eb6c716a0e5403aa348b9bc7e46cbe1b308cde Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 17:56:02 +0530 Subject: [PATCH 04/11] processorIDCounter -> simpleProcessorIDCounter as processorIDCounter is already being used in batch span processor --- sdk/trace/simple_span_processor.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 0b53812e587..24600f711e3 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -52,12 +52,12 @@ func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor { return ssp } -var processorIDCounter atomic.Int64 +var simpleProcessorIDCounter atomic.Int64 -// nextProcessorID returns an identifier for this simple span processor, +// nextSimpleProcessorID returns an identifier for this simple span processor, // starting with 0 and incrementing by 1 each time it is called. -func nextProcessorID() int64 { - return processorIDCounter.Add(1) - 1 +func nextSimpleProcessorID() int64 { + return simpleProcessorIDCounter.Add(1) - 1 } // configureSelfObservability configures metrics for the simple span processor. @@ -67,7 +67,7 @@ func (ssp *simpleSpanProcessor) configureSelfObservability() { } ssp.selfObservabilityEnabled = true ssp.componentNameAttr = semconv.OTelComponentName( - fmt.Sprintf("%s/%d", otelconv.ComponentTypeSimpleSpanProcessor, nextProcessorID())) + fmt.Sprintf("%s/%d", otelconv.ComponentTypeSimpleSpanProcessor, nextSimpleProcessorID())) meter := otel.GetMeterProvider().Meter( selfObsScopeName, metric.WithInstrumentationVersion(sdk.Version()), From f8aa01e98fe502e7d70121c75452020563086b32 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 19:48:01 +0530 Subject: [PATCH 05/11] test cases for simple span processor --- sdk/trace/simple_span_processor_test.go | 148 ++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go index 4aa4ea39c88..a401284ad31 100644 --- a/sdk/trace/simple_span_processor_test.go +++ b/sdk/trace/simple_span_processor_test.go @@ -6,11 +6,23 @@ package trace import ( "context" "errors" + "strconv" "sync" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + semconv "go.opentelemetry.io/otel/semconv/v1.36.0" + "go.opentelemetry.io/otel/semconv/v1.36.0/otelconv" ) type simpleTestExporter struct { @@ -34,6 +46,17 @@ func (t *simpleTestExporter) Shutdown(ctx context.Context) error { } } +var _ SpanExporter = (*failingTestExporter)(nil) + +type failingTestExporter struct { + simpleTestExporter +} + +func (f *failingTestExporter) ExportSpans(ctx context.Context, spans []ReadOnlySpan) error { + _ = f.simpleTestExporter.ExportSpans(ctx, spans) + return errors.New("failed to export spans") +} + var _ SpanExporter = (*simpleTestExporter)(nil) func TestNewSimpleSpanProcessor(t *testing.T) { @@ -168,3 +191,128 @@ func TestSimpleSpanProcessorShutdownHonorsContextCancel(t *testing.T) { t.Errorf("SimpleSpanProcessor.Shutdown did not return %v, got %v", want, got) } } + +func TestSimpleSpanProcessorSelfObservability(t *testing.T) { + tests := []struct { + name string + enabled bool + exporter SpanExporter + assertMetrics func(t *testing.T, rm metricdata.ResourceMetrics) + }{ + { + name: "Disabled", + enabled: false, + exporter: &simpleTestExporter{}, + assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Empty(t, rm.ScopeMetrics) + }, + }, + { + name: "Enabled", + enabled: true, + exporter: &simpleTestExporter{}, + assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Len(t, rm.ScopeMetrics, 1) + sm := rm.ScopeMetrics[0] + + want := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "go.opentelemetry.io/otel/sdk/trace", + Version: sdk.Version(), + SchemaURL: semconv.SchemaURL, + }, + Metrics: []metricdata.Metrics{ + { + Name: otelconv.SDKProcessorSpanProcessed{}.Name(), + Description: otelconv.SDKProcessorSpanProcessed{}.Description(), + Unit: otelconv.SDKProcessorSpanProcessed{}.Unit(), + Data: metricdata.Sum[int64]{ + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + Attributes: attribute.NewSet( + semconv.OTelComponentName("simple_span_processor/0"), + semconv.OTelComponentTypeKey.String("simple_span_processor"), + ), + }, + }, + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + }, + }, + }, + } + + metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp()) + }, + }, + { + name: "Enabled, Exporter error", + enabled: true, + exporter: &failingTestExporter{ + simpleTestExporter: simpleTestExporter{}, + }, + assertMetrics: func(t *testing.T, rm metricdata.ResourceMetrics) { + assert.Len(t, rm.ScopeMetrics, 1) + sm := rm.ScopeMetrics[0] + + want := metricdata.ScopeMetrics{ + Scope: instrumentation.Scope{ + Name: "go.opentelemetry.io/otel/sdk/trace", + Version: sdk.Version(), + SchemaURL: semconv.SchemaURL, + }, + Metrics: []metricdata.Metrics{ + { + Name: otelconv.SDKProcessorSpanProcessed{}.Name(), + Description: otelconv.SDKProcessorSpanProcessed{}.Description(), + Unit: otelconv.SDKProcessorSpanProcessed{}.Unit(), + Data: metricdata.Sum[int64]{ + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + Attributes: attribute.NewSet( + semconv.OTelComponentName("simple_span_processor/1"), + semconv.OTelComponentTypeKey.String("simple_span_processor"), + semconv.ErrorTypeKey.String("*errors.errorString"), + ), + }, + }, + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + }, + }, + }, + } + + metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp()) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Setenv("OTEL_GO_X_SELF_OBSERVABILITY", strconv.FormatBool(test.enabled)) + + original := otel.GetMeterProvider() + t.Cleanup(func() { otel.SetMeterProvider(original) }) + + r := metric.NewManualReader() + mp := metric.NewMeterProvider( + metric.WithReader(r), + metric.WithView(dropSpanMetricsView), + ) + otel.SetMeterProvider(mp) + + ssp := NewSimpleSpanProcessor(test.exporter) + tp := basicTracerProvider(t) + tp.RegisterSpanProcessor(ssp) + startSpan(tp, test.name).End() + + var rm metricdata.ResourceMetrics + require.NoError(t, r.Collect(context.Background(), &rm)) + test.assertMetrics(t, rm) + }) + + } +} From 5c0fafb0442604bf44cdf1836f0a3344dadeab94 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 19:54:35 +0530 Subject: [PATCH 06/11] fix lint --- sdk/trace/simple_span_processor_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go index a401284ad31..d415859cf08 100644 --- a/sdk/trace/simple_span_processor_test.go +++ b/sdk/trace/simple_span_processor_test.go @@ -313,6 +313,5 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { require.NoError(t, r.Collect(context.Background(), &rm)) test.assertMetrics(t, rm) }) - } } From 6f72bbb327bf1d07c8bb1e1fee3c9c44e4a57c2a Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 22:14:54 +0530 Subject: [PATCH 07/11] review comments - separate changelog entry - flatten self observability initialization - reset simpleProcessorIDCounter so tests can be run in parallel and order doesn't matter --- CHANGELOG.md | 6 +++-- sdk/trace/simple_span_processor.go | 34 +++++++++++++------------ sdk/trace/simple_span_processor_test.go | 3 ++- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c8dea46c8..c486fd0e554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,14 +49,16 @@ The next release will require at least [Go 1.24]. - The `go.opentelemetry.io/otel/semconv/v1.36.0` package. The package contains semantic conventions from the `v1.36.0` version of the OpenTelemetry Semantic Conventions. See the [migration documentation](./semconv/v1.36.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.34.0.`(#7032) -- Add experimental self-observability span, simple span processor, and batch span processor metrics in `go.opentelemetry.io/otel/sdk/trace`. - Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #6393, #7162) +- Add experimental self-observability span and batch span processor metrics in `go.opentelemetry.io/otel/sdk/trace`. + Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7027, #6393) - Add native histogram exemplar support in `go.opentelemetry.io/otel/exporters/prometheus`. (#6772) - Add experimental self-observability log metrics in `go.opentelemetry.io/otel/sdk/log`. Check the `go.opentelemetry.io/otel/sdk/log/internal/x` package documentation for more information. (#7121) - Add experimental self-observability trace exporter metrics in `go.opentelemetry.io/otel/exporters/stdout/stdouttrace`. Check the `go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x` package documentation for more information. (#7133) - Support testing of [Go 1.25]. (#7187) +- Add experimental self-observability simple span processor metrics in `go.opentelemetry.io/otel/sdk/trace`. + Check the `go.opentelemetry.io/otel/sdk/trace/internal/x` package documentation for more information. (#7162) ### Changed diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 24600f711e3..8d6f35a2797 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -43,9 +43,22 @@ var _ SpanProcessor = (*simpleSpanProcessor)(nil) // use instead. func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor { ssp := &simpleSpanProcessor{ - exporter: exporter, + exporter: exporter, + selfObservabilityEnabled: x.SelfObservability.Enabled(), + } + + if ssp.selfObservabilityEnabled { + ssp.componentNameAttr = semconv.OTelComponentName( + fmt.Sprintf("%s/%d", otelconv.ComponentTypeSimpleSpanProcessor, nextSimpleProcessorID())) + + var err error + ssp.spansProcessedCounter, err = newInst() + if err != nil { + msg := "failed to create self-observability metrics for simple span processor: %w" + err := fmt.Errorf(msg, err) + otel.Handle(err) + } } - ssp.configureSelfObservability() global.Warn("SimpleSpanProcessor is not recommended for production use, consider using BatchSpanProcessor instead.") @@ -60,25 +73,14 @@ func nextSimpleProcessorID() int64 { return simpleProcessorIDCounter.Add(1) - 1 } -// configureSelfObservability configures metrics for the simple span processor. -func (ssp *simpleSpanProcessor) configureSelfObservability() { - if !x.SelfObservability.Enabled() { - return - } - ssp.selfObservabilityEnabled = true - ssp.componentNameAttr = semconv.OTelComponentName( - fmt.Sprintf("%s/%d", otelconv.ComponentTypeSimpleSpanProcessor, nextSimpleProcessorID())) +func newInst() (otelconv.SDKProcessorSpanProcessed, error) { meter := otel.GetMeterProvider().Meter( selfObsScopeName, metric.WithInstrumentationVersion(sdk.Version()), metric.WithSchemaURL(semconv.SchemaURL), ) - - var err error - ssp.spansProcessedCounter, err = otelconv.NewSDKProcessorSpanProcessed(meter) - if err != nil { - otel.Handle(err) - } + spansProcessedCounter, err := otelconv.NewSDKProcessorSpanProcessed(meter) + return spansProcessedCounter, err } // OnStart does nothing. diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go index d415859cf08..99796043372 100644 --- a/sdk/trace/simple_span_processor_test.go +++ b/sdk/trace/simple_span_processor_test.go @@ -272,7 +272,7 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { { Value: 1, Attributes: attribute.NewSet( - semconv.OTelComponentName("simple_span_processor/1"), + semconv.OTelComponentName("simple_span_processor/0"), semconv.OTelComponentTypeKey.String("simple_span_processor"), semconv.ErrorTypeKey.String("*errors.errorString"), ), @@ -312,6 +312,7 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { var rm metricdata.ResourceMetrics require.NoError(t, r.Collect(context.Background(), &rm)) test.assertMetrics(t, rm) + simpleProcessorIDCounter.Store(0) // reset simpleProcessorIDCounter }) } } From ec0b88a759440715b28b5288157a56a7b8fef831 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 22:53:09 +0530 Subject: [PATCH 08/11] review comments - use sync.Pool to amortize allocation for metric attrs --- sdk/trace/simple_span_processor.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 8d6f35a2797..9c91e7fb252 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -19,6 +19,17 @@ import ( "go.opentelemetry.io/otel/semconv/v1.36.0/otelconv" ) +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 + }, +} + // simpleSpanProcessor is a SpanProcessor that synchronously sends all // completed Spans to a trace.Exporter immediately. type simpleSpanProcessor struct { @@ -92,17 +103,22 @@ func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) { defer ssp.exporterMu.Unlock() if ssp.exporter != nil && s.SpanContext().TraceFlags().IsSampled() { - attrs := []attribute.KeyValue{ + attrs := measureAttrsPool.Get().(*[]attribute.KeyValue) + defer func() { + *attrs = (*attrs)[:0] // reset the slice for reuse + measureAttrsPool.Put(attrs) + }() + *attrs = append(*attrs, ssp.componentNameAttr, - ssp.spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeSimpleSpanProcessor), - } + ssp.spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeSimpleSpanProcessor)) + err := ssp.exporter.ExportSpans(context.Background(), []ReadOnlySpan{s}) if err != nil { otel.Handle(err) - attrs = append(attrs, semconv.ErrorType(err)) + *attrs = append(*attrs, semconv.ErrorType(err)) } if ssp.selfObservabilityEnabled { - ssp.spansProcessedCounter.Add(context.Background(), 1, attrs...) + ssp.spansProcessedCounter.Add(context.Background(), 1, *attrs...) } } } From 966d7d1bfa62eab73bc53ea4db5c087a998b8882 Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 23:07:19 +0530 Subject: [PATCH 09/11] review comments - use sync.Pool to amortize allocation for metric attrs - pass context with span to ensure metric is recorded with correct span context --- sdk/trace/simple_span_processor.go | 6 +++++- sdk/trace/simple_span_processor_test.go | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 9c91e7fb252..962b126faa3 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -17,6 +17,7 @@ import ( "go.opentelemetry.io/otel/sdk/trace/internal/x" semconv "go.opentelemetry.io/otel/semconv/v1.36.0" "go.opentelemetry.io/otel/semconv/v1.36.0/otelconv" + "go.opentelemetry.io/otel/trace" ) var measureAttrsPool = sync.Pool{ @@ -118,7 +119,10 @@ func (ssp *simpleSpanProcessor) OnEnd(s ReadOnlySpan) { *attrs = append(*attrs, semconv.ErrorType(err)) } if ssp.selfObservabilityEnabled { - ssp.spansProcessedCounter.Add(context.Background(), 1, *attrs...) + // Add the span to the context to ensure the metric is recorded + // with the correct span context. + ctx := trace.ContextWithSpanContext(context.Background(), s.SpanContext()) + ssp.spansProcessedCounter.Add(ctx, 1, *attrs...) } } } diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go index 99796043372..e0245919e0c 100644 --- a/sdk/trace/simple_span_processor_test.go +++ b/sdk/trace/simple_span_processor_test.go @@ -243,7 +243,7 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { }, } - metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp()) + metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) }, }, { @@ -285,7 +285,7 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { }, } - metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp()) + metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) }, }, } From df565dfe7ff17313fcce33bd68b61638db25795c Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Sat, 16 Aug 2025 23:37:17 +0530 Subject: [PATCH 10/11] run `make precommit` --- sdk/trace/simple_span_processor_test.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sdk/trace/simple_span_processor_test.go b/sdk/trace/simple_span_processor_test.go index e0245919e0c..bc5f8926dca 100644 --- a/sdk/trace/simple_span_processor_test.go +++ b/sdk/trace/simple_span_processor_test.go @@ -243,7 +243,13 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { }, } - metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) + metricdatatest.AssertEqual( + t, + want, + sm, + metricdatatest.IgnoreTimestamp(), + metricdatatest.IgnoreExemplars(), + ) }, }, { @@ -285,7 +291,13 @@ func TestSimpleSpanProcessorSelfObservability(t *testing.T) { }, } - metricdatatest.AssertEqual(t, want, sm, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreExemplars()) + metricdatatest.AssertEqual( + t, + want, + sm, + metricdatatest.IgnoreTimestamp(), + metricdatatest.IgnoreExemplars(), + ) }, }, } From 97c11ff10cb3037f445c3f5633848e3b3226184c Mon Sep 17 00:00:00 2001 From: Mahendra Bishnoi Date: Thu, 28 Aug 2025 23:37:37 +0530 Subject: [PATCH 11/11] fix issue caused by merge with main due to function name collision --- sdk/trace/simple_span_processor.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/trace/simple_span_processor.go b/sdk/trace/simple_span_processor.go index 962b126faa3..7465a5d54f9 100644 --- a/sdk/trace/simple_span_processor.go +++ b/sdk/trace/simple_span_processor.go @@ -64,7 +64,7 @@ func NewSimpleSpanProcessor(exporter SpanExporter) SpanProcessor { fmt.Sprintf("%s/%d", otelconv.ComponentTypeSimpleSpanProcessor, nextSimpleProcessorID())) var err error - ssp.spansProcessedCounter, err = newInst() + ssp.spansProcessedCounter, err = newSpanProcessedInst() if err != nil { msg := "failed to create self-observability metrics for simple span processor: %w" err := fmt.Errorf(msg, err) @@ -85,7 +85,7 @@ func nextSimpleProcessorID() int64 { return simpleProcessorIDCounter.Add(1) - 1 } -func newInst() (otelconv.SDKProcessorSpanProcessed, error) { +func newSpanProcessedInst() (otelconv.SDKProcessorSpanProcessed, error) { meter := otel.GetMeterProvider().Meter( selfObsScopeName, metric.WithInstrumentationVersion(sdk.Version()),