-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: sdk/trace: span processed metric for simple span processor #7374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MrAlias
merged 42 commits into
open-telemetry:main
from
mahendrabishnoi2:sdk-trace-simple-processor-metrics
Oct 17, 2025
+415
−1
Merged
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
f7c180e
implementation of `otel.sdk.processor.span.processed` metric for simp…
mahendrabishnoi2 f15ae4e
add correct PR number
mahendrabishnoi2 d743f6a
fix component type
mahendrabishnoi2 b686526
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 17eb6c7
processorIDCounter -> simpleProcessorIDCounter as processorIDCounter …
mahendrabishnoi2 f8aa01e
test cases for simple span processor
mahendrabishnoi2 5c0fafb
fix lint
mahendrabishnoi2 6f72bbb
review comments
mahendrabishnoi2 ec0b88a
review comments
mahendrabishnoi2 966d7d1
review comments
mahendrabishnoi2 df565df
run `make precommit`
mahendrabishnoi2 053ca17
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 97c11ff
fix issue caused by merge with main due to function name collision
mahendrabishnoi2 89e258e
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 9753d0f
fix changelog -> move to unreleased section
mahendrabishnoi2 754b9bb
move sdk observability out of simpleSpanProcessor struct
mahendrabishnoi2 5545ac6
run make precommit
mahendrabishnoi2 a4ab409
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 e855db1
Record -> SpanProcessed
mahendrabishnoi2 558eb43
add tests for simple_span_processor in observ package
mahendrabishnoi2 323bc44
fix formatting
mahendrabishnoi2 8e9b90b
add benchmark test
mahendrabishnoi2 1cca2be
fix formatting
mahendrabishnoi2 870ed15
fix formatting
mahendrabishnoi2 927d13b
review comment - rename sspComponentId to sspComponentID
mahendrabishnoi2 0c5fa21
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 9c29553
capture span processed metric irrespective of whether its sampled or …
mahendrabishnoi2 9bcc692
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 36be6aa
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 83f6d2a
review comments, optimize memory allocation by precomputing in succes…
mahendrabishnoi2 1028666
rename self observability -> observability
mahendrabishnoi2 bd07d99
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 7f1ab1b
fix CHANGELOG.md
mahendrabishnoi2 7ae87cb
retrigger build
mahendrabishnoi2 c44abb6
reduce allocations in happy path to 0 (from 1)
mahendrabishnoi2 747e794
address review comments
mahendrabishnoi2 88f0c5f
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 01e65b1
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 d833159
Merge branch 'main' into sdk-trace-simple-processor-metrics
mahendrabishnoi2 c08bb45
remove file
mahendrabishnoi2 69b3a14
fix import
mahendrabishnoi2 3a89a59
Merge branch 'main' into sdk-trace-simple-processor-metrics
MrAlias File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package observ // import "go.opentelemetry.io/otel/sdk/trace/internal/observ" | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "sync" | ||
|
|
||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/attribute" | ||
| "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.37.0" | ||
| "go.opentelemetry.io/otel/semconv/v1.37.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 | ||
| }, | ||
| } | ||
|
|
||
| // SSP is the instrumentation for an OTel SDK SimpleSpanProcessor. | ||
| type SSP struct { | ||
| spansProcessedCounter metric.Int64Counter | ||
| addOpts []metric.AddOption | ||
| attrs []attribute.KeyValue | ||
| } | ||
|
|
||
| // SSPComponentName returns the component name attribute for a | ||
| // SimpleSpanProcessor with the given ID. | ||
| func SSPComponentName(id int64) attribute.KeyValue { | ||
| t := otelconv.ComponentTypeSimpleSpanProcessor | ||
| name := fmt.Sprintf("%s/%d", t, id) | ||
| return semconv.OTelComponentName(name) | ||
| } | ||
|
|
||
| // NewSSP returns instrumentation for an OTel SDK SimpleSpanProcessor with the | ||
| // provided ID. | ||
| // | ||
| // If the experimental observability is disabled, nil is returned. | ||
| func NewSSP(id int64) (*SSP, error) { | ||
mahendrabishnoi2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if !x.Observability.Enabled() { | ||
| return nil, nil | ||
| } | ||
|
|
||
| meter := otel.GetMeterProvider().Meter( | ||
| ScopeName, | ||
| metric.WithInstrumentationVersion(sdk.Version()), | ||
| metric.WithSchemaURL(SchemaURL), | ||
| ) | ||
| spansProcessedCounter, err := otelconv.NewSDKProcessorSpanProcessed(meter) | ||
| if err != nil { | ||
| err = fmt.Errorf("failed to create SSP processed spans metric: %w", err) | ||
| } | ||
|
|
||
| componentName := SSPComponentName(id) | ||
| componentType := spansProcessedCounter.AttrComponentType(otelconv.ComponentTypeSimpleSpanProcessor) | ||
| attrs := []attribute.KeyValue{componentName, componentType} | ||
| addOpts := []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(attrs...))} | ||
|
|
||
| return &SSP{ | ||
| spansProcessedCounter: spansProcessedCounter.Inst(), | ||
| addOpts: addOpts, | ||
| attrs: attrs, | ||
| }, err | ||
| } | ||
|
|
||
| // SpanProcessed records that a span has been processed by the SimpleSpanProcessor. | ||
| // If err is non-nil, it records the processing error as an attribute. | ||
| func (ssp *SSP) SpanProcessed(ctx context.Context, err error) { | ||
| ssp.spansProcessedCounter.Add(ctx, 1, ssp.addOption(err)...) | ||
| } | ||
|
|
||
| func (ssp *SSP) addOption(err error) []metric.AddOption { | ||
| if err == nil { | ||
MrAlias marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return ssp.addOpts | ||
| } | ||
| attrs := measureAttrsPool.Get().(*[]attribute.KeyValue) | ||
| defer func() { | ||
| *attrs = (*attrs)[:0] // reset the slice for reuse | ||
| measureAttrsPool.Put(attrs) | ||
| }() | ||
| *attrs = append(*attrs, ssp.attrs...) | ||
| *attrs = append(*attrs, semconv.ErrorType(err)) | ||
| // Do not inefficiently make a copy of attrs by using | ||
| // WithAttributes instead of WithAttributeSet. | ||
| return []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(*attrs...))} | ||
| } | ||
127 changes: 127 additions & 0 deletions
127
sdk/trace/internal/observ/simple_span_processor_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // Copyright The OpenTelemetry Authors | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| package observ_test | ||
|
|
||
| import ( | ||
| "errors" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/attribute" | ||
| "go.opentelemetry.io/otel/metric/noop" | ||
| "go.opentelemetry.io/otel/sdk/trace/internal/observ" | ||
| semconv "go.opentelemetry.io/otel/semconv/v1.37.0" | ||
| ) | ||
|
|
||
| const sspComponentID = 0 | ||
|
|
||
| func TestSSPComponentName(t *testing.T) { | ||
| got := observ.SSPComponentName(10) | ||
| want := semconv.OTelComponentName("simple_span_processor/10") | ||
| assert.Equal(t, want, got) | ||
| } | ||
|
|
||
| func TestNewSSPError(t *testing.T) { | ||
| t.Setenv("OTEL_GO_X_OBSERVABILITY", "true") | ||
|
|
||
| orig := otel.GetMeterProvider() | ||
| t.Cleanup(func() { otel.SetMeterProvider(orig) }) | ||
|
|
||
| mp := &errMeterProvider{err: assert.AnError} | ||
| otel.SetMeterProvider(mp) | ||
|
|
||
| _, err := observ.NewSSP(sspComponentID) | ||
| require.ErrorIs(t, err, assert.AnError, "new instrument errors") | ||
| assert.ErrorContains(t, err, "create SSP processed spans metric") | ||
| } | ||
|
|
||
| func TestNewSSPDisabled(t *testing.T) { | ||
| ssp, err := observ.NewSSP(sspComponentID) | ||
| assert.NoError(t, err) | ||
| assert.Nil(t, ssp) | ||
| } | ||
|
|
||
| func TestSSPSpanProcessed(t *testing.T) { | ||
| ctx := t.Context() | ||
| collect := setup(t) | ||
| ssp, err := observ.NewSSP(sspComponentID) | ||
| assert.NoError(t, err) | ||
|
|
||
| ssp.SpanProcessed(ctx, nil) | ||
| check(t, collect(), processed(dPt(sspSet(), 1))) | ||
| ssp.SpanProcessed(ctx, nil) | ||
| ssp.SpanProcessed(ctx, nil) | ||
| check(t, collect(), processed(dPt(sspSet(), 3))) | ||
|
|
||
| processErr := errors.New("error processing span") | ||
| ssp.SpanProcessed(ctx, processErr) | ||
| check(t, collect(), processed( | ||
| dPt(sspSet(), 3), | ||
| dPt(sspSet(semconv.ErrorType(processErr)), 1), | ||
| )) | ||
| } | ||
|
|
||
| func BenchmarkSSP(b *testing.B) { | ||
| b.Setenv("OTEL_GO_X_OBSERVABILITY", "true") | ||
|
|
||
| newSSP := func(b *testing.B) *observ.SSP { | ||
| b.Helper() | ||
| ssp, err := observ.NewSSP(sspComponentID) | ||
| require.NoError(b, err) | ||
| require.NotNil(b, ssp) | ||
| return ssp | ||
| } | ||
|
|
||
| b.Run("SpanProcessed", func(b *testing.B) { | ||
| orig := otel.GetMeterProvider() | ||
| b.Cleanup(func() { | ||
| otel.SetMeterProvider(orig) | ||
| }) | ||
|
|
||
| // Ensure deterministic benchmark by using noop meter. | ||
| otel.SetMeterProvider(noop.NewMeterProvider()) | ||
|
|
||
| ssp := newSSP(b) | ||
| ctx := b.Context() | ||
|
|
||
| b.ResetTimer() | ||
| b.ReportAllocs() | ||
| b.RunParallel(func(pb *testing.PB) { | ||
| for pb.Next() { | ||
| ssp.SpanProcessed(ctx, nil) | ||
| } | ||
| }) | ||
| }) | ||
|
|
||
| b.Run("SpanProcessedWithError", func(b *testing.B) { | ||
| orig := otel.GetMeterProvider() | ||
| b.Cleanup(func() { | ||
| otel.SetMeterProvider(orig) | ||
| }) | ||
|
|
||
| // Ensure deterministic benchmark by using noop meter. | ||
| otel.SetMeterProvider(noop.NewMeterProvider()) | ||
|
|
||
| ssp := newSSP(b) | ||
| ctx := b.Context() | ||
| processErr := errors.New("error processing span") | ||
|
|
||
| b.ResetTimer() | ||
| b.ReportAllocs() | ||
| b.RunParallel(func(pb *testing.PB) { | ||
| for pb.Next() { | ||
| ssp.SpanProcessed(ctx, processErr) | ||
| } | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
| func sspSet(attrs ...attribute.KeyValue) attribute.Set { | ||
| return attribute.NewSet(append([]attribute.KeyValue{ | ||
| semconv.OTelComponentTypeSimpleSpanProcessor, | ||
| observ.SSPComponentName(sspComponentID), | ||
| }, attrs...)...) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.