From 6d6f47020b6bd8565862b91283184f304375c114 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 11 Dec 2024 10:46:13 -0500 Subject: [PATCH 1/4] Add 'integration' field to abandonedSpanCandidate --- ddtrace/tracer/abandonedspans.go | 24 +++++++++++++++++------- ddtrace/tracer/abandonedspans_test.go | 9 ++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/ddtrace/tracer/abandonedspans.go b/ddtrace/tracer/abandonedspans.go index defad41831..71edceb23f 100644 --- a/ddtrace/tracer/abandonedspans.go +++ b/ddtrace/tracer/abandonedspans.go @@ -14,6 +14,7 @@ import ( "sync/atomic" "time" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -77,27 +78,36 @@ type abandonedSpanCandidate struct { TraceID, SpanID uint64 Start int64 Finished bool + Integration string } func newAbandonedSpanCandidate(s *span, finished bool) *abandonedSpanCandidate { + var component string + if v, ok := s.Meta[ext.Component]; ok { + component = v + } else { + component = "manual" + } // finished is explicit instead of implicit as s.finished may be not set // at the moment of calling this method. // Also, locking is not required as it's called while the span is already locked or it's // being initialized. - return &abandonedSpanCandidate{ - Name: s.Name, - TraceID: s.TraceID, - SpanID: s.SpanID, - Start: s.Start, - Finished: finished, + c := &abandonedSpanCandidate{ + Name: s.Name, + TraceID: s.TraceID, + SpanID: s.SpanID, + Start: s.Start, + Finished: finished, + Integration: component, } + return c } // String takes a span and returns a human-readable string representing that span. func (s *abandonedSpanCandidate) String() string { age := now() - s.Start a := fmt.Sprintf("%d sec", age/1e9) - return fmt.Sprintf("[name: %s, span_id: %d, trace_id: %d, age: %s],", s.Name, s.SpanID, s.TraceID, a) + return fmt.Sprintf("[name: %s, integration: %s, span_id: %d, trace_id: %d, age: %s],", s.Name, s.Integration, s.SpanID, s.TraceID, a) } type abandonedSpansDebugger struct { diff --git a/ddtrace/tracer/abandonedspans_test.go b/ddtrace/tracer/abandonedspans_test.go index 745f03f222..cd29998215 100644 --- a/ddtrace/tracer/abandonedspans_test.go +++ b/ddtrace/tracer/abandonedspans_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" @@ -58,7 +59,13 @@ func assertProcessedSpans(assert *assert.Assertions, t *tracer, startedSpans, fi func formatSpanString(s *span) string { s.Lock() - msg := fmt.Sprintf("[name: %s, span_id: %d, trace_id: %d, age: %s],", s.Name, s.SpanID, s.TraceID, spanAge(s)) + var integration string + if v, ok := s.Meta[ext.Component]; ok { + integration = v + } else { + integration = "manual" + } + msg := fmt.Sprintf("[name: %s, integration: %s, span_id: %d, trace_id: %d, age: %s],", s.Name, integration, s.SpanID, s.TraceID, spanAge(s)) s.Unlock() return msg } From 7db6a3e806aa1de59640113cbb84775a16803de5 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Wed, 11 Dec 2024 14:55:06 -0500 Subject: [PATCH 2/4] Report abandoned spans as a metric --- ddtrace/tracer/abandonedspans.go | 4 +++ ddtrace/tracer/abandonedspans_test.go | 46 +++++++++++++++++++++++++++ internal/statsdtest/statsdtest.go | 38 ++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/ddtrace/tracer/abandonedspans.go b/ddtrace/tracer/abandonedspans.go index 71edceb23f..9ccf1ca045 100644 --- a/ddtrace/tracer/abandonedspans.go +++ b/ddtrace/tracer/abandonedspans.go @@ -15,6 +15,7 @@ import ( "time" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" + "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/internal" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) @@ -302,6 +303,9 @@ func formatAbandonedSpans(b *bucket[uint64, *abandonedSpanCandidate], interval * if interval != nil && curTime-s.Start < interval.Nanoseconds() { continue } + if t, ok := internal.GetGlobalTracer().(*tracer); ok { + t.statsd.Incr("datadog.tracer.abandoned_spans", []string{"name:" + s.Name, "integration:" + s.Integration}, 1) + } spanCount++ msg := s.String() sb.WriteString(msg) diff --git a/ddtrace/tracer/abandonedspans_test.go b/ddtrace/tracer/abandonedspans_test.go index cd29998215..565b722302 100644 --- a/ddtrace/tracer/abandonedspans_test.go +++ b/ddtrace/tracer/abandonedspans_test.go @@ -14,6 +14,7 @@ import ( "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext" "gopkg.in/DataDog/dd-trace-go.v1/internal/log" + "gopkg.in/DataDog/dd-trace-go.v1/internal/statsdtest" "gopkg.in/DataDog/dd-trace-go.v1/internal/version" "github.com/stretchr/testify/assert" @@ -70,6 +71,51 @@ func formatSpanString(s *span) string { return msg } +func TestAbandonedSpansMetric(t *testing.T) { + assert := assert.New(t) + var tg statsdtest.TestStatsdClient + tp := new(log.RecordLogger) + tickerInterval = 100 * time.Millisecond + t.Run("finished", func(t *testing.T) { + tp.Reset() + tg.Reset() + defer setTestTime()() + tracer, _, _, stop := startTestTracer(t, WithLogger(tp), WithDebugSpansMode(500*time.Millisecond), withStatsdClient(&tg)) + defer stop() + s := tracer.StartSpan("operation", StartTime(spanStart)).(*span) + s.Finish() + assertProcessedSpans(assert, tracer, 1, 1) + assert.Empty(tg.GetCallsByName("datadog.tracer.abandoned_spans")) + }) + t.Run("open", func(t *testing.T) { + tp.Reset() + tg.Reset() + defer setTestTime()() + tracer, _, _, stop := startTestTracer(t, WithLogger(tp), WithDebugSpansMode(500*time.Millisecond), withStatsdClient(&tg)) + defer stop() + tracer.StartSpan("operation", StartTime(spanStart)) + assertProcessedSpans(assert, tracer, 1, 0) + calls := tg.GetCallsByName("datadog.tracer.abandoned_spans") + assert.Len(calls, 1) + call := calls[0] + assert.Equal([]string{"name:operation", "integration:manual"}, call.Tags()) + }) + t.Run("both", func(t *testing.T) { + tp.Reset() + tg.Reset() + defer setTestTime()() + tracer, _, _, stop := startTestTracer(t, WithLogger(tp), WithDebugSpansMode(500*time.Millisecond), withStatsdClient(&tg)) + defer stop() + sf := tracer.StartSpan("op", StartTime(spanStart)).(*span) + sf.Finish() + s := tracer.StartSpan("op2", StartTime(spanStart)).(*span) + assertProcessedSpans(assert, tracer, 2, 1) + calls := tg.GetCallsByName("datadog.tracer.abandoned_spans") + assert.Len(calls, 1) + s.Finish() + }) +} + func TestReportAbandonedSpans(t *testing.T) { assert := assert.New(t) tp := new(log.RecordLogger) diff --git a/internal/statsdtest/statsdtest.go b/internal/statsdtest/statsdtest.go index 8845e6465c..cc5cc8a30f 100644 --- a/internal/statsdtest/statsdtest.go +++ b/internal/statsdtest/statsdtest.go @@ -46,6 +46,18 @@ type TestStatsdCall struct { rate float64 } +func (t TestStatsdCall) Name() string { + return t.name +} + +func (t TestStatsdCall) Tags() []string { + return t.tags +} + +func (t TestStatsdCall) IntVal() int64 { + return t.intVal +} + func (tg *TestStatsdClient) addCount(name string, value int64) { tg.mu.Lock() defer tg.mu.Unlock() @@ -214,6 +226,32 @@ func (tg *TestStatsdClient) CallsByName() map[string]int { return counts } +func (tg *TestStatsdClient) GetCallsByName(name string) (calls []TestStatsdCall) { + tg.mu.RLock() + defer tg.mu.RUnlock() + for _, c := range tg.gaugeCalls { + if c.Name() == name { + calls = append(calls, c) + } + } + for _, c := range tg.incrCalls { + if c.Name() == name { + calls = append(calls, c) + } + } + for _, c := range tg.countCalls { + if c.Name() == name { + calls = append(calls, c) + } + } + for _, c := range tg.timingCalls { + if c.Name() == name { + calls = append(calls, c) + } + } + return calls +} + func (tg *TestStatsdClient) Counts() map[string]int64 { tg.mu.RLock() defer tg.mu.RUnlock() From afbfa04b977cefb2da1b48da66366ee2847da0c9 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Thu, 12 Dec 2024 11:06:39 -0500 Subject: [PATCH 3/4] add tests --- ddtrace/tracer/abandonedspans_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ddtrace/tracer/abandonedspans_test.go b/ddtrace/tracer/abandonedspans_test.go index 565b722302..a929961df2 100644 --- a/ddtrace/tracer/abandonedspans_test.go +++ b/ddtrace/tracer/abandonedspans_test.go @@ -93,12 +93,12 @@ func TestAbandonedSpansMetric(t *testing.T) { defer setTestTime()() tracer, _, _, stop := startTestTracer(t, WithLogger(tp), WithDebugSpansMode(500*time.Millisecond), withStatsdClient(&tg)) defer stop() - tracer.StartSpan("operation", StartTime(spanStart)) + tracer.StartSpan("operation", StartTime(spanStart), Tag(ext.Component, "some_integration_name")) assertProcessedSpans(assert, tracer, 1, 0) calls := tg.GetCallsByName("datadog.tracer.abandoned_spans") assert.Len(calls, 1) call := calls[0] - assert.Equal([]string{"name:operation", "integration:manual"}, call.Tags()) + assert.Equal([]string{"name:operation", "integration:some_integration_name"}, call.Tags()) }) t.Run("both", func(t *testing.T) { tp.Reset() From 91f52b4d059a0a4b348b39b84a34c71b456da2e4 Mon Sep 17 00:00:00 2001 From: Mikayla Toffler Date: Fri, 20 Dec 2024 10:21:15 -0500 Subject: [PATCH 4/4] more merge conflicts --- internal/statsdtest/statsdtest.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/statsdtest/statsdtest.go b/internal/statsdtest/statsdtest.go index 8997f421dc..e31cdb4a9f 100644 --- a/internal/statsdtest/statsdtest.go +++ b/internal/statsdtest/statsdtest.go @@ -233,7 +233,8 @@ func (tg *TestStatsdClient) CallsByName() map[string]int { return counts } -<<<<<<< HEAD +// GetCallsByName returns a slice of TestStatsdCalls with the provided name on the TestStatsdClient +// It's useful if you want to use any TestStatsdCall method calls on the result(s) func (tg *TestStatsdClient) GetCallsByName(name string) (calls []TestStatsdCall) { tg.mu.RLock() defer tg.mu.RUnlock() @@ -258,7 +259,9 @@ func (tg *TestStatsdClient) GetCallsByName(name string) (calls []TestStatsdCall) } } return calls -======= +} + +// FilterCallsByName returns a slice of TestStatsdCalls with the provided name, from the list of provided TestStatsdCalls func FilterCallsByName(calls []TestStatsdCall, name string) []TestStatsdCall { var matches []TestStatsdCall for _, c := range calls { @@ -267,7 +270,6 @@ func FilterCallsByName(calls []TestStatsdCall, name string) []TestStatsdCall { } } return matches ->>>>>>> main } func (tg *TestStatsdClient) Counts() map[string]int64 {