Skip to content
1 change: 1 addition & 0 deletions .codespellignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ ans
nam
valu
thirdparty
addOpt
85 changes: 68 additions & 17 deletions exporters/stdout/stdouttrace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -91,39 +93,66 @@ 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) {
var success int64
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.
Expand All @@ -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())
}

Expand Down
29 changes: 19 additions & 10 deletions exporters/stdout/stdouttrace/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
{
Expand All @@ -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)
}
Loading