1
0
mirror of https://github.com/open-telemetry/opentelemetry-go.git synced 2025-11-23 22:34:47 +02:00
Files
opentelemetry-go/exporters/stdout/stdouttrace/trace.go
Tyler Yahn 88d3fedf1a Optimize the return type of ExportSpans (#7405)
Do not allocate a return function from `ExportSpans` to the heap. Use
the added `ExportOp` type instead.

### Benchmarks

#### `stdouttrace`

```terminal
goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/exporters/stdout/stdouttrace
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
                                      │ main.bmark.results │ stdouttrace-optimize-end.bmark.results │
                                      │       sec/op       │     sec/op       vs base               │
ExporterExportSpans/Observability-8            23.37µ ± 2%       22.79µ ± 3%  -2.50% (p=0.025 n=10)
ExporterExportSpans/NoObservability-8          23.07µ ± 7%       22.29µ ± 1%  -3.38% (p=0.000 n=10)
geomean                                        23.22µ            22.54µ       -2.94%

                                      │ main.bmark.results │ stdouttrace-optimize-end.bmark.results │
                                      │        B/op        │      B/op        vs base               │
ExporterExportSpans/Observability-8           4.253Ki ± 0%      4.190Ki ± 0%  -1.47% (p=0.000 n=10)
ExporterExportSpans/NoObservability-8         3.975Ki ± 0%      3.975Ki ± 0%       ~ (p=0.474 n=10)
geomean                                       4.111Ki           4.081Ki       -0.74%

                                      │ main.bmark.results │ stdouttrace-optimize-end.bmark.results │
                                      │     allocs/op      │   allocs/op    vs base                 │
ExporterExportSpans/Observability-8             67.00 ± 0%      66.00 ± 0%  -1.49% (p=0.000 n=10)
ExporterExportSpans/NoObservability-8           64.00 ± 0%      64.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                         65.48           64.99       -0.75%
¹ all samples are equal
```

#### `stdouttrace/internal/observ`

```terminal

goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
                                          │ main.bmark.results │ stdouttrace-optimize-end.bmark.results │
                                          │       sec/op       │     sec/op      vs base                │
InstrumentationExportSpans/NoError-8               197.9n ± 7%      153.3n ± 5%  -22.51% (p=0.000 n=10)
InstrumentationExportSpans/PartialError-8          754.4n ± 6%      663.2n ± 6%  -12.08% (p=0.001 n=10)
InstrumentationExportSpans/FullError-8             772.8n ± 4%      669.2n ± 4%  -13.39% (p=0.000 n=10)
geomean                                            486.8n           408.3n       -16.13%

                                          │ main.bmark.results │ stdouttrace-optimize-end.bmark.results  │
                                          │        B/op        │    B/op     vs base                     │
InstrumentationExportSpans/NoError-8                64.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
InstrumentationExportSpans/PartialError-8           280.0 ± 0%   216.0 ± 0%   -22.86% (p=0.000 n=10)
InstrumentationExportSpans/FullError-8              280.0 ± 0%   216.0 ± 0%   -22.86% (p=0.000 n=10)
geomean                                             171.2                    ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean

                                          │ main.bmark.results │ stdouttrace-optimize-end.bmark.results  │
                                          │     allocs/op      │ allocs/op   vs base                     │
InstrumentationExportSpans/NoError-8                1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
InstrumentationExportSpans/PartialError-8           3.000 ± 0%   2.000 ± 0%   -33.33% (p=0.000 n=10)
InstrumentationExportSpans/FullError-8              3.000 ± 0%   2.000 ± 0%   -33.33% (p=0.000 n=10)
geomean                                             2.080                    ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean
```
2025-09-25 09:07:15 -07:00

122 lines
2.6 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"time"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
var zeroTime time.Time
var _ trace.SpanExporter = &Exporter{}
// New creates an Exporter with the passed options.
func New(options ...Option) (*Exporter, error) {
cfg := newConfig(options...)
enc := json.NewEncoder(cfg.Writer)
if cfg.PrettyPrint {
enc.SetIndent("", "\t")
}
exporter := &Exporter{
encoder: enc,
timestamps: cfg.Timestamps,
}
var err error
exporter.inst, err = observ.NewInstrumentation(counter.NextExporterID())
return exporter, err
}
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
type Exporter struct {
encoder *json.Encoder
encoderMu sync.Mutex
timestamps bool
stoppedMu sync.RWMutex
stopped bool
inst *observ.Instrumentation
}
// 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.inst != nil {
op := e.inst.ExportSpans(ctx, len(spans))
defer func() { op.End(success, err) }()
}
if err := ctx.Err(); err != nil {
return err
}
e.stoppedMu.RLock()
stopped := e.stopped
e.stoppedMu.RUnlock()
if stopped {
return nil
}
if len(spans) == 0 {
return nil
}
stubs := tracetest.SpanStubsFromReadOnlySpans(spans)
e.encoderMu.Lock()
defer e.encoderMu.Unlock()
for i := range stubs {
stub := &stubs[i]
// Remove timestamps
if !e.timestamps {
stub.StartTime = zeroTime
stub.EndTime = zeroTime
for j := range stub.Events {
ev := &stub.Events[j]
ev.Time = zeroTime
}
}
// Encode span stubs, one by one
if e := e.encoder.Encode(stub); e != nil {
err = errors.Join(err, fmt.Errorf("failed to encode span %d: %w", i, e))
continue
}
success++
}
return err
}
// Shutdown is called to stop the exporter, it performs no action.
func (e *Exporter) Shutdown(context.Context) error {
e.stoppedMu.Lock()
e.stopped = true
e.stoppedMu.Unlock()
return nil
}
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
func (e *Exporter) MarshalLog() any {
return struct {
Type string
WithTimestamps bool
}{
Type: "stdout",
WithTimestamps: e.timestamps,
}
}