Skip to content

Commit

Permalink
Test SDK Tracer
Browse files Browse the repository at this point in the history
Move the tracer type and related functionality to its own file to help
discoverability.

Test the tracer type including its concurrent safety (part of open-telemetry#1297).
  • Loading branch information
MrAlias committed Nov 18, 2024
1 parent 5007dc3 commit ee51e80
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 80 deletions.
80 changes: 0 additions & 80 deletions sdk/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package sdk

import (
"context"
"encoding/json"
"fmt"
"reflect"
Expand Down Expand Up @@ -44,85 +43,6 @@ func (p tracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tr
}
}

type tracer struct {
noop.Tracer

name, schemaURL, version string
}

var _ trace.Tracer = tracer{}

func (t tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
var psc trace.SpanContext
span := &span{sampled: true}

// Ask eBPF for sampling decision and span context info.
t.start(ctx, span, &psc, &span.sampled, &span.spanContext)

ctx = trace.ContextWithSpan(ctx, span)

if span.sampled {
// Only build traces if sampled.
cfg := trace.NewSpanStartConfig(opts...)
span.traces, span.span = t.traces(ctx, name, cfg, span.spanContext, psc)
}

return ctx, span
}

// Expected to be implemented in eBPF.
//
//go:noinline
func (t *tracer) start(
ctx context.Context,
spanPtr *span,
psc *trace.SpanContext,
sampled *bool,
sc *trace.SpanContext,
) {
start(ctx, spanPtr, psc, sampled, sc)
}

// start is used for testing.
var start = func(context.Context, *span, *trace.SpanContext, *bool, *trace.SpanContext) {}

func (t tracer) traces(ctx context.Context, name string, cfg trace.SpanConfig, sc, psc trace.SpanContext) (*telemetry.Traces, *telemetry.Span) {
span := &telemetry.Span{
TraceID: telemetry.TraceID(sc.TraceID()),
SpanID: telemetry.SpanID(sc.SpanID()),
Flags: uint32(sc.TraceFlags()),
TraceState: sc.TraceState().String(),
ParentSpanID: telemetry.SpanID(psc.SpanID()),
Name: name,
Kind: spanKind(cfg.SpanKind()),
Attrs: convAttrs(cfg.Attributes()),
Links: convLinks(cfg.Links()),
}

if t := cfg.Timestamp(); !t.IsZero() {
span.StartTime = cfg.Timestamp()
} else {
span.StartTime = time.Now()
}

return &telemetry.Traces{
ResourceSpans: []*telemetry.ResourceSpans{
{
ScopeSpans: []*telemetry.ScopeSpans{
{
Scope: &telemetry.Scope{
Name: t.name,
Version: t.version,
},
Spans: []*telemetry.Span{span},
SchemaURL: t.schemaURL,
},
},
},
},
}, span
}

func spanKind(kind trace.SpanKind) telemetry.SpanKind {
switch kind {
case trace.SpanKindInternal:
Expand Down
93 changes: 93 additions & 0 deletions sdk/tracer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package sdk

import (
"context"
"time"

"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"

"go.opentelemetry.io/auto/sdk/internal/telemetry"
)

type tracer struct {
noop.Tracer

name, schemaURL, version string
}

var _ trace.Tracer = tracer{}

func (t tracer) Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
var psc trace.SpanContext
span := &span{sampled: true}

// Ask eBPF for sampling decision and span context info.
t.start(ctx, span, &psc, &span.sampled, &span.spanContext)

ctx = trace.ContextWithSpan(ctx, span)

if span.sampled {
// Only build traces if sampled.
cfg := trace.NewSpanStartConfig(opts...)
span.traces, span.span = t.traces(ctx, name, cfg, span.spanContext, psc)
}

return ctx, span
}

// Expected to be implemented in eBPF.
//
//go:noinline
func (t *tracer) start(
ctx context.Context,
spanPtr *span,
psc *trace.SpanContext,
sampled *bool,
sc *trace.SpanContext,
) {
start(ctx, spanPtr, psc, sampled, sc)
}

// start is used for testing.
var start = func(context.Context, *span, *trace.SpanContext, *bool, *trace.SpanContext) {}

func (t tracer) traces(ctx context.Context, name string, cfg trace.SpanConfig, sc, psc trace.SpanContext) (*telemetry.Traces, *telemetry.Span) {
span := &telemetry.Span{
TraceID: telemetry.TraceID(sc.TraceID()),
SpanID: telemetry.SpanID(sc.SpanID()),
Flags: uint32(sc.TraceFlags()),
TraceState: sc.TraceState().String(),
ParentSpanID: telemetry.SpanID(psc.SpanID()),
Name: name,
Kind: spanKind(cfg.SpanKind()),
Attrs: convAttrs(cfg.Attributes()),
Links: convLinks(cfg.Links()),
}

if t := cfg.Timestamp(); !t.IsZero() {
span.StartTime = cfg.Timestamp()
} else {
span.StartTime = time.Now()
}

return &telemetry.Traces{
ResourceSpans: []*telemetry.ResourceSpans{
{
ScopeSpans: []*telemetry.ScopeSpans{
{
Scope: &telemetry.Scope{
Name: t.name,
Version: t.version,
},
Spans: []*telemetry.Span{span},
SchemaURL: t.schemaURL,
},
},
},
},
}, span
}
82 changes: 82 additions & 0 deletions sdk/tracer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package sdk

import (
"context"
"strconv"
"sync"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/trace"
)

const tName = "tracer.name"

func TestTracerStartPropagatesOrigCtx(t *testing.T) {
t.Parallel()

type ctxKey struct{}
var key ctxKey
val := "value"

ctx := context.WithValue(context.Background(), key, val)
ctx, _ = TracerProvider().Tracer(tName).Start(ctx, "span.name")

assert.Equal(t, val, ctx.Value(key))
}

func TestTracerStartReturnsNonNilSpan(t *testing.T) {
t.Parallel()

tr := TracerProvider().Tracer(tName)
_, s := tr.Start(context.Background(), "span.name")
assert.NotNil(t, s)
}

func TestTracerStartAddsSpanToCtx(t *testing.T) {
t.Parallel()

tr := TracerProvider().Tracer(tName)
ctx, s := tr.Start(context.Background(), "span.name")

assert.Same(t, s, trace.SpanFromContext(ctx))
}

func TestTracerConcurrentSafe(t *testing.T) {
t.Parallel()

const goroutines = 10

ctx := context.Background()
run := func(tracer trace.Tracer) <-chan struct{} {
done := make(chan struct{})

go func(tr trace.Tracer) {
defer close(done)

var wg sync.WaitGroup
for i := 0; i < goroutines; i++ {
wg.Add(1)
go func(name string) {
defer wg.Done()
_, _ = tr.Start(ctx, name)
}("span" + strconv.Itoa(i))
}

wg.Wait()
}(tracer)

return done
}

assert.NotPanics(t, func() {
tp := TracerProvider()
done0, done1 := run(tp.Tracer("t0")), run(tp.Tracer("t1"))

<-done0
<-done1
})
}

0 comments on commit ee51e80

Please sign in to comment.