Skip to content

Commit

Permalink
slow tracing update proposal: invert role of tags/selector
Browse files Browse the repository at this point in the history
  • Loading branch information
elv-gilles committed Nov 19, 2024
1 parent e5687f8 commit 7fdb0d1
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 64 deletions.
4 changes: 2 additions & 2 deletions util/ctxutil/noop.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ func (n noop) Go(fn func()) {
go fn()
}

func (n noop) InitTracing(_ string, _ ...string) trace.Span {
func (n noop) InitTracing(_ string, _ ...trace.SpanAcceptor) trace.Span {
return trace.NoopSpan{}
}

func (n noop) StartSpan(_ string, _ ...trace.SpanAcceptor) trace.Span {
func (n noop) StartSpan(_ string, _ ...string) trace.Span {
return trace.NoopSpan{}
}

Expand Down
16 changes: 8 additions & 8 deletions util/ctxutil/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,17 @@ type ContextStack interface {
Go(fn func())

// InitTracing initializes tracing for the current goroutine with a root span created through the given tracer.
// The root span will record operations that are started as spans accepting the given tags.
// An empty or nil tags parameter implies all spans are started.
InitTracing(spanName string, tags ...string) trace.Span
// The root span will record operations that are started as spans accepted by the given tags acceptor.
// An empty or nil accept parameter implies all spans are started.
InitTracing(spanName string, accept ...trace.SpanAcceptor) trace.Span

// StartSpan starts a new span and pushes its context onto the stack of the current goroutine. The span pops the
// context upon calling span.End().
//
// Usage:
// span := r.cs.StartSpan("my span")
// defer span.End()
StartSpan(spanName string, accept ...trace.SpanAcceptor) trace.Span
StartSpan(spanName string, tags ...string) trace.Span

// Span retrieves the goroutine's current span.
Span() trace.Span
Expand Down Expand Up @@ -168,8 +168,8 @@ func (c *contextStack) Go(fn func()) {

}

func (c *contextStack) InitTracing(spanName string, tags ...string) trace.Span {
ctx, sp := trace.StartRootSpan(c.Ctx(), spanName, tags...)
func (c *contextStack) InitTracing(spanName string, accept ...trace.SpanAcceptor) trace.Span {
ctx, sp := trace.StartRootSpan(c.Ctx(), spanName, accept...)
release := c.Push(ctx)
return &span{
gid: goutil.GoID(),
Expand All @@ -178,14 +178,14 @@ func (c *contextStack) InitTracing(spanName string, tags ...string) trace.Span {
}
}

func (c *contextStack) StartSpan(spanName string, acceptor ...trace.SpanAcceptor) trace.Span {
func (c *contextStack) StartSpan(spanName string, tags ...string) trace.Span {
ctx := c.Ctx()
parentSpan := trace.SpanFromContext(ctx)
if !parentSpan.IsRecording() {
// fast path if tracing is disabled: no need to start a (noop) span and push its dummy ctx onto the stack...
return trace.NoopSpan{}
}
ctx, sp := parentSpan.Start(ctx, spanName, acceptor...)
ctx, sp := parentSpan.Start(ctx, spanName, tags...)
if !sp.IsRecording() {
return trace.NoopSpan{}
}
Expand Down
8 changes: 6 additions & 2 deletions util/traceutil/trace/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ type contextKey struct{}
var activeSpanKey = contextKey{}

// StartRootSpan starts a new top-level span and registers it with the given context.
func StartRootSpan(ctx context.Context, name string, tags ...string) (context.Context, Span) {
sub := newSpan(name, tags)
func StartRootSpan(ctx context.Context, name string, accept ...SpanAcceptor) (context.Context, Span) {
var acceptor SpanAcceptor
if len(accept) > 0 {
acceptor = accept[0]
}
sub := newSpan(name, acceptor)
return ContextWithSpan(ctx, sub), sub
}

Expand Down
79 changes: 47 additions & 32 deletions util/traceutil/trace/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ type Span interface {
// IsRecording returns true if the span is active and recording events is enabled.
IsRecording() bool

// Tags returns the tags ...
Tags() []string
// Acceptor returns a string view of the tags acceptor
Acceptor() string

// Json converts the span to its JSON representation.
Json() string

// Start creates and starts a sub-span. The returned context holds a reference to the sub-span and may be retrieved
// with SpanFromContext.
Start(ctx context.Context, name string, acceptor ...SpanAcceptor) (context.Context, Span)
Start(ctx context.Context, name string, tags ...string) (context.Context, Span)

// Attributes returns the span's attribute set.
Attributes() map[string]interface{}
Expand All @@ -55,19 +55,30 @@ type Span interface {

type SpanAcceptor interface {
Accept(tags []string) bool
String() string
}

type Str string

func (s Str) Accept(tags []string) bool {
return s == "" || len(tags) == 0 || sliceutil.Contains(tags, string(s))
return sliceutil.Contains(tags, string(s))
}

func (s Str) String() string { return string(s) }

type NoStr string

func (s NoStr) Accept(tags []string) bool {
return s == "" || len(tags) == 0 || !sliceutil.Contains(tags, string(s))
}
func (s NoStr) String() string { return "!" + string(s) }

type acceptAllSpans struct{}

func (a acceptAllSpans) Accept(tags []string) bool { return true }
func (a acceptAllSpans) String() string { return "" }

var AcceptAllSpans acceptAllSpans

type ExtendedSpan interface {
// StartTime returns the start time of the span.
Expand Down Expand Up @@ -107,35 +118,39 @@ type Event struct {

type NoopSpan struct{}

func (n NoopSpan) Start(ctx context.Context, _ string, _ ...SpanAcceptor) (context.Context, Span) {
func (n NoopSpan) Start(ctx context.Context, _ string, _ ...string) (context.Context, Span) {
return ctx, n
}

func (n NoopSpan) End() {}
func (n NoopSpan) Attribute(name string, val interface{}) {}
func (n NoopSpan) Event(name string, attributes map[string]interface{}) {}
func (n NoopSpan) IsRecording() bool { return false }
func (n NoopSpan) Tags() []string { return nil }
func (n NoopSpan) Json() string { return "" }
func (n NoopSpan) Attributes() map[string]interface{} { return nil }
func (n NoopSpan) Events() []*Event { return nil }
func (n NoopSpan) FindByName(string) Span { return nil }
func (n NoopSpan) StartTime() utc.UTC { return utc.Zero }
func (n NoopSpan) EndTime() utc.UTC { return utc.Zero }
func (n NoopSpan) Duration() time.Duration { return 0 }
func (n NoopSpan) MaxDuration() time.Duration { return 0 }
func (n NoopSpan) MarshalExtended() bool { return false }
func (n NoopSpan) SetMarshalExtended() {}
func (n NoopSpan) SlowCutoff() time.Duration { return 0 }
func (n NoopSpan) SetSlowCutoff(cutoff time.Duration) {}
func (n NoopSpan) FilterSpans(_ func(rec Span) bool) (Span, bool) { return nil, false }
func (n NoopSpan) End() {}
func (n NoopSpan) Attribute(_ string, _ interface{}) {}
func (n NoopSpan) Event(_ string, _ map[string]interface{}) {}
func (n NoopSpan) IsRecording() bool { return false }
func (n NoopSpan) Acceptor() string { return "" }
func (n NoopSpan) Json() string { return "" }
func (n NoopSpan) Attributes() map[string]interface{} { return nil }
func (n NoopSpan) Events() []*Event { return nil }
func (n NoopSpan) FindByName(string) Span { return nil }
func (n NoopSpan) StartTime() utc.UTC { return utc.Zero }
func (n NoopSpan) EndTime() utc.UTC { return utc.Zero }
func (n NoopSpan) Duration() time.Duration { return 0 }
func (n NoopSpan) MaxDuration() time.Duration { return 0 }
func (n NoopSpan) MarshalExtended() bool { return false }
func (n NoopSpan) SetMarshalExtended() {}
func (n NoopSpan) SlowCutoff() time.Duration { return 0 }
func (n NoopSpan) SetSlowCutoff(cutoff time.Duration) {}
func (n NoopSpan) FilterSpans(_ func(rec Span) bool) (Span, bool) { return nil, false }

// ---------------------------------------------------------------------------------------------------------------------

func newSpan(name string, tags []string) *RecordingSpan {
func newSpan(name string, accept SpanAcceptor) *RecordingSpan {
var acceptor SpanAcceptor = AcceptAllSpans
if accept != nil {
acceptor = accept
}
s := &RecordingSpan{
startTime: utc.Now(),
tags: tags,
acceptor: acceptor,
}
s.Data.Name = name
s.Data.Start = s.startTime.String()
Expand All @@ -152,7 +167,7 @@ type RecordingSpan struct {
// extended must not be protected by a lock, as child spans may look up to their parent span to
// determine marshalling behavior
extended bool
tags []string
acceptor SpanAcceptor
}

type recordingData struct {
Expand All @@ -170,12 +185,12 @@ type recordingExtendedData struct {
End string `json:"end"`
}

func (s *RecordingSpan) Start(ctx context.Context, name string, acceptor ...SpanAcceptor) (context.Context, Span) {
if len(acceptor) > 0 && acceptor[0] != nil && !acceptor[0].Accept(s.Tags()) {
func (s *RecordingSpan) Start(ctx context.Context, name string, tags ...string) (context.Context, Span) {
if !s.acceptor.Accept(tags) {
return ctx, NoopSpan{}
}

sub := newSpan(name, s.tags)
sub := newSpan(name, s.acceptor)
sub.Parent = s

s.mutex.Lock()
Expand Down Expand Up @@ -238,8 +253,8 @@ func (s *RecordingSpan) IsRecording() bool {
return true
}

func (s *RecordingSpan) Tags() []string {
return s.tags
func (s *RecordingSpan) Acceptor() string {
return s.acceptor.String()
}

func (s *RecordingSpan) Json() string {
Expand Down Expand Up @@ -403,7 +418,7 @@ func (s *RecordingSpan) copy() *RecordingSpan {
endTime: s.endTime,
duration: s.duration,
extended: s.extended,
tags: s.tags,
acceptor: s.acceptor,
}
return c
}
20 changes: 10 additions & 10 deletions util/traceutil/traceutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ func current() ctxutil.ContextStack {
//
// If slowOnly is true, only spans that are explicitly created with StartSlowSpan wiill be recorded,
// and even then only persisted if they end up being slower than expected.
func InitTracing(spanName string, tags ...string) trace.Span {
return current().InitTracing(spanName, tags...)
func InitTracing(spanName string, accept ...trace.SpanAcceptor) trace.Span {
return current().InitTracing(spanName, accept...)
}

// StartSpan creates new sub-span of the goroutine's current span or a noop
// span if there is no current span.
func StartSpan(spanName string, acceptor ...trace.SpanAcceptor) trace.Span {
return current().StartSpan(spanName, acceptor...)
func StartSpan(spanName string, tags ...string) trace.Span {
return current().StartSpan(spanName, tags...)
}

// WithSpan creates a new sub-span of the goroutine's current span and executes
// the given function within the sub-span.
func WithSpan(spanName string, fn func() error) error {
span := current().StartSpan(spanName)
func WithSpan(spanName string, fn func() error, tags ...string) error {
span := current().StartSpan(spanName, tags...)
defer span.End()

err := fn()
Expand All @@ -54,20 +54,20 @@ func Ctx() context.Context {

// StartSubSpan creates new sub-span of the context's current span or a noop
// span if there is no current span.
func StartSubSpan(ctx context.Context, spanName string) (context.Context, trace.Span) {
func StartSubSpan(ctx context.Context, spanName string, tags ...string) (context.Context, trace.Span) {
if ctx == nil {
return nil, trace.NoopSpan{}
}
return trace.SpanFromContext(ctx).Start(ctx, spanName)
return trace.SpanFromContext(ctx).Start(ctx, spanName, tags...)
}

// WithSubSpan executes the given function in a new sub-span of the context's
// current span or a noop span if there is no current span.
func WithSubSpan(ctx context.Context, spanName string, fn func(ctx context.Context) error) error {
func WithSubSpan(ctx context.Context, spanName string, fn func(ctx context.Context) error, tags ...string) error {
if ctx == nil {
ctx = context.Background()
}
ctx, span := trace.SpanFromContext(ctx).Start(ctx, spanName)
ctx, span := trace.SpanFromContext(ctx).Start(ctx, spanName, tags...)
defer span.End()
err := fn(ctx)
if err != nil {
Expand Down
19 changes: 9 additions & 10 deletions util/traceutil/traceutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/stretchr/testify/require"

"github.com/eluv-io/common-go/util/sliceutil"
"github.com/eluv-io/utc-go"

"github.com/eluv-io/common-go/util/traceutil"
Expand Down Expand Up @@ -41,22 +40,22 @@ func TestStartSubSpan(t *testing.T) {
}

func TestSlowSpanInit(t *testing.T) {
rootSp := traceutil.InitTracing("slow-span-test", "slow")
rootSp := traceutil.InitTracing("slow-span-test", trace.Str("slow"))
require.True(t, rootSp.IsRecording())
require.True(t, sliceutil.Contains(rootSp.Tags(), "slow"))
require.True(t, strings.Contains(rootSp.Acceptor(), "slow"))

span := traceutil.StartSpan("should-not-appear-red", trace.Str("red"))
span := traceutil.StartSpan("should-not-appear-red", "red")
require.NotNil(t, span)
require.False(t, span.IsRecording())

span = traceutil.StartSpan("should-not-appear", trace.NoStr("slow"))
span = traceutil.StartSpan("should-not-appear")
require.NotNil(t, span)
require.False(t, span.IsRecording())

slowSp := traceutil.StartSpan("should-appear", trace.Str("slow"))
slowSp := traceutil.StartSpan("should-appear", "slow")
require.NotNil(t, slowSp)
require.True(t, slowSp.IsRecording())
require.True(t, sliceutil.Contains(slowSp.Tags(), "slow"))
require.True(t, strings.Contains(slowSp.Acceptor(), "slow"))

slowSp.SetSlowCutoff(500 * time.Millisecond)
time.Sleep(1 * time.Second)
Expand Down Expand Up @@ -104,13 +103,13 @@ func TestSlowSpanInit(t *testing.T) {
func TestInitTracing(t *testing.T) {
rootSp := traceutil.InitTracing("init-tracing-test")
require.True(t, rootSp.IsRecording())
require.False(t, sliceutil.Contains(rootSp.Tags(), "slow"))
require.False(t, strings.Contains(rootSp.Acceptor(), "slow"))

span := traceutil.StartSpan("should-appear-regular", nil)
span := traceutil.StartSpan("should-appear-regular")
require.NotNil(t, span)
require.True(t, span.IsRecording())

slowSp := traceutil.StartSpan("should-appear-slow", trace.Str("slow"))
slowSp := traceutil.StartSpan("should-appear-slow", "slow")
require.NotNil(t, slowSp)
require.True(t, slowSp.IsRecording())
slowSp.Attribute("attr-1", "arbitrary-unique-value")
Expand Down

0 comments on commit 7fdb0d1

Please sign in to comment.