Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
220ad1c
feat(ddtrace/tracer): promote span fields from meta
darccio Mar 12, 2026
4d323b6
feat(ddtrace/tracer): use span.env
darccio Mar 12, 2026
f0107ae
feat(ddtrace/tracer): use span.spanKind
darccio Mar 12, 2026
a59baa9
feat(ddtrace/tracer): evolve promoted fields to state-aware values wi…
darccio Mar 12, 2026
5b2d9e0
feat(ddtrace/tracer): encode promoted fields instead of looking up th…
darccio Mar 12, 2026
2ab1efd
feat(ddtrace/tracer): introduce API to tagValue to avoid leaking impl…
darccio Mar 13, 2026
ef61c9d
chore(ddtrace/tracer): preparing refactor to internalize
darccio Mar 13, 2026
49cf5f3
chore(ddtrace/tracer): internalize SpanAttributes
darccio Mar 13, 2026
b021620
feat(ddtrace/tracer): enhancing API by using presence bitmap and acce…
darccio Mar 13, 2026
9bc84d9
chore: make lint/go/fix
darccio Mar 16, 2026
5f64898
chore(ddtrace/tracer): remove unexported alias
darccio Mar 16, 2026
21dfaa2
feat(ddtrace/tracer): add language to SpanAttributes
darccio Mar 16, 2026
d1664fc
test(ddtrace/tracer): add tests and benchmarks for SpanAttributes
darccio Mar 16, 2026
ab6f412
feat(ddtrace/tracer): avoid double-encoding for promoted fields
darccio Mar 16, 2026
55c7693
feat(ddtrace/tracer): COW-aware SpanAttributes; revert ext.Language
darccio Mar 16, 2026
2cbde38
chore(ddtrace/ext): drop Language
darccio Mar 16, 2026
9db4e60
feat(ddtrace/tracer): introduce spanMeta to group map and SpanAttribu…
darccio Mar 16, 2026
4179372
fix(ddtrace/tracer): regenerate msgp code
darccio Mar 16, 2026
6eb24be
fix(ddtrace/mocktracer): update spanStart linkname signature with uns…
darccio Mar 16, 2026
823d13b
fix(ddtrace/tracer): reduce introduced allocations
darccio Mar 16, 2026
63a0bdf
fix(ddtrace/tracer): avoid nil dereference on tests
darccio Mar 17, 2026
aff9637
fix(ddtrace/tracer): optimize code layout for favour improved inlining
darccio Mar 17, 2026
f5e90d3
fix(ddtrace/tracer): ensure checklocks is ok
darccio Mar 17, 2026
581e57f
fix(ddtrace/tracer): revert DecodeMsg to a simpler state because fide…
darccio Mar 17, 2026
2973d1c
feat(ddtrace/tracer): hide spanMeta's implementation details behind a…
darccio Mar 18, 2026
2106eee
refactor(ddtrace/tracer): move spanMeta to ddtrace/tracer/internal
darccio Mar 18, 2026
b6fcf9e
fix(ddtrace/tracer): detected mishaps
darccio Mar 18, 2026
a720748
chore(ddtrace/tracer): make lint/go/fix
darccio Mar 18, 2026
8246258
feat(ddtrace/tracer): reduce allocations on hot paths; reduce CPU cyc…
darccio Mar 19, 2026
40dbd10
fix(ddtrace/tracer): modernize using maps.Copy on spanMeta.Merge
darccio Mar 19, 2026
d31f246
fix(ddtrace/tracer): clean up; ensure new API is inlinable
darccio Mar 19, 2026
e809732
fix(ddtrace/tracer): try to reduce allocations, again (meta init hint…
darccio Mar 19, 2026
aad2032
fix(ddtrace/tracer): fixing SpanMeta.Merge to avoid bad allocations
darccio Mar 19, 2026
851fe39
fix(ddtrace/tracer): pool StatSpanConfig; ensure SpanMeta.Merge doesn…
darccio Mar 20, 2026
a373301
chore(ddtrace/tracer): encapsulate pooling logic around the statSpanC…
darccio Mar 20, 2026
d4c7ec4
fix(ddtrace/tracer): avoid v1 payload corruption by exposing attrs pr…
darccio Mar 20, 2026
9fd77aa
chore(ddtrace/tracer): revert to simpler design
darccio Mar 20, 2026
b15fe08
fix(ddtrace/tracer): inline promoted attributes into meta map to redu…
darccio Mar 20, 2026
3c9e3fa
chore(ddtrace/tracer): break down big conditional into individual guards
darccio Mar 20, 2026
80a9c92
chore(ddtrace/tracer): happy path must be left aligned
darccio Mar 20, 2026
35dd1f0
chore(ddtrace/tracer): final touches
darccio Mar 20, 2026
d4ad3e8
fix(ddtrace/tracer): run SpanMeta.Inline before SpanContext.finish to…
darccio Mar 23, 2026
1bd2a4a
fix(ddtrace/tracer): hold span lock on CIVis when reading inner span
darccio Mar 23, 2026
091e0f4
fix(ddtrace/tracer): adapt TestStatsIncludeServiceSource to new SpanMeta
darccio Mar 23, 2026
e6b737f
fix(ddtrace/tracer): remove redundant assert.RWMutextLocked
darccio Mar 23, 2026
5b45c5c
fix(ddtrace/tracer): add inlined bool, RangeInlined, and Attr to opti…
darccio Mar 23, 2026
ef0e563
refactor(ddtrace/tracer): use `tracerinternal` instead of `tinternal`
darccio Mar 24, 2026
5fe4407
refactor(ddtrace/tracer): remove `SpanMeta.Inline()`
darccio Mar 24, 2026
b3d0f7f
feat(ddtrace/tracer): simplify API and don't expose internals
darccio Mar 24, 2026
3f89622
refactor(ddtrace/tracer): drop `*Map` methods on SpanMeta
darccio Mar 24, 2026
1b510d8
fix(ddtrace/tracer): adapt merged code
darccio Mar 25, 2026
aa55e43
perf(ddtrace/tracer): reduce SpanMeta overhead on hot paths
darccio Mar 26, 2026
35ddce1
fix(ddtrace/tracer): fix leftovers for supportsLinks
darccio Mar 26, 2026
4bab6e1
feat(ddtrace/tracer): another attempt
darccio Mar 26, 2026
7921b27
chore(ddtrace/tracer): move dangling comment to its right place
darccio Mar 26, 2026
b52bcf0
feat(ddtrace/tracer): ensure that Delete is inlineable; simplify API
darccio Mar 26, 2026
d50dd48
chore(ddtrace/tracer): adapt TestServiceSource
darccio Mar 27, 2026
c70a7bb
chore(ddtrace/tracer): improve samplingPriorityPtr docs
darccio Mar 27, 2026
aeead6f
feat(ddtrace/tracer): promote language to attribute
darccio Mar 27, 2026
d80aad0
feat(ddtrace/tracer): implement locking on SpanMeta
darccio Mar 27, 2026
c1a3543
fix(ddtrace/tracer): reduce mutex contantion leveraging inline as ato…
darccio Mar 27, 2026
eece436
revert: go back to lock-free implementation; add SpanMeta.Finish to a…
darccio Mar 27, 2026
78586fe
fix(ddtrace/tracer): demote component and span.kind; adopt read-only …
darccio Mar 27, 2026
9020723
fix(ddtrace/tracer): use atomic inline; remove misleading comment
darccio Mar 27, 2026
423355d
fix(ddtrace/tracer): avoid race condition moving meta.Finish before c…
darccio Mar 27, 2026
525b29a
chore(ddtrace/tracer): structural and safety improvements
darccio Apr 15, 2026
8fcdf46
fix(ddtrace/tracer): remove map merging code based on wrong assumptio…
darccio Apr 15, 2026
50afbf1
Merge remote-tracking branch 'origin' into dario.castane/apmlp-856/pr…
darccio Apr 15, 2026
57a2f21
feat(ddtrace/tracer): simplify after merging main
darccio Apr 15, 2026
7b626d1
chore(ddtrace/tracer): go fmt
darccio Apr 15, 2026
4ae9555
fix(ddtrace/tracer): allow to adjust which `SpanMeta.Map` a consumer …
darccio Apr 16, 2026
ef024b0
Merge remote-tracking branch 'origin' into dario.castane/apmlp-856/pr…
darccio Apr 16, 2026
21cbe1d
fix(ddtrace/tracer): update OTLP exporter code to new promoted fields…
darccio Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ddtrace/mocktracer/mockspan.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"fmt"
"testing"
"time"
_ "unsafe" // Needed for go:linkname directive.
"unsafe" // Needed for go:linkname directive.

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -21,10 +21,10 @@ import (
)

//go:linkname spanStart github.com/DataDog/dd-trace-go/v2/ddtrace/tracer.spanStart
func spanStart(operationName string, options ...tracer.StartSpanOption) *tracer.Span
func spanStart(operationName string, sharedAttrs unsafe.Pointer, options ...tracer.StartSpanOption) *tracer.Span

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should go level higher here if it is possible. sharedAttrs should be an implementation detail.

Suggested change
func spanStart(operationName string, sharedAttrs unsafe.Pointer, options ...tracer.StartSpanOption) *tracer.Span
func spanStart(operationName string, meta spanMeta, options ...tracer.StartSpanOption) *tracer.Span

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kakkoyun mocktracer doesn't have visibility of the unexported spanMeta type. How should this be done?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😭 mocktracer strikes again. We should find a better solution for it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kakkoyun It's called inspectable tracer 😁 #4512

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Kemal; would #4512 unblock this for us?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since #4512 is very close. Maybe we should merge it first and then this one.


func newSpan(operationName string, cfg *tracer.StartSpanConfig) *tracer.Span {
return spanStart(operationName, func(c *tracer.StartSpanConfig) {
return spanStart(operationName, nil, func(c *tracer.StartSpanConfig) {
*c = *cfg
})
}
Expand Down
2 changes: 1 addition & 1 deletion ddtrace/tracer/abandonedspans.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type abandonedSpanCandidate struct {
// +checklocksignore — Called while span is locked or during initialization.
func newAbandonedSpanCandidate(s *Span, finished bool) *abandonedSpanCandidate {
var component string
if v, ok := s.meta[ext.Component]; ok {
if v, ok := s.meta.Get(ext.Component); ok {
component = v
} else {
component = "manual"
Expand Down
2 changes: 1 addition & 1 deletion ddtrace/tracer/civisibility_payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func benchmarkCiVisibilityPayloadThroughput(count int) func(*testing.B) {
return func(b *testing.B) {
p := newCiVisibilityPayload()
s := newBasicSpan("X")
s.meta["key"] = strings.Repeat("X", 10*1024)
s.meta.Set("key", strings.Repeat("X", 10*1024))
e := getCiVisibilityEvent(s)
events := make(ciVisibilityEvents, count)
for i := range count {
Expand Down
9 changes: 7 additions & 2 deletions ddtrace/tracer/civisibility_tslv.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ type ciVisibilityEvent struct {
// +checklocksignore — CI visibility event: reads span fields after SetTag releases lock.
func (e *ciVisibilityEvent) SetTag(key string, value any) {
e.span.SetTag(key, value)
e.Content.Meta = e.span.meta
e.Content.Metrics = e.span.metrics
}

Expand Down Expand Up @@ -210,6 +209,12 @@ func (e *ciVisibilityEvent) SetBaggageItem(key, val string) {
// opts - Optional finish options.
func (e *ciVisibilityEvent) Finish(opts ...FinishOption) {
e.span.Finish(opts...)
// Rebuild Content.Meta once with the final span state, under the span
// lock to avoid racing with the serialization worker.
e.span.mu.Lock()
e.Content.Meta = e.span.meta.Map(true)
e.Content.Metrics = e.span.metrics
e.span.mu.Unlock()
}

// Context returns the span context of the event's span.
Expand Down Expand Up @@ -411,7 +416,7 @@ func createTslvSpan(span *Span) tslvSpan {
Duration: span.duration,
ParentID: span.parentID,
Error: span.error,
Meta: span.meta,
Meta: span.meta.Map(true),
Metrics: span.metrics,
}
}
Expand Down
14 changes: 8 additions & 6 deletions ddtrace/tracer/data_streams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ func TestTrackDataStreamsTransactionTagsSpan(t *testing.T) {

s, ok := SpanFromContext(ctx)
require.True(t, ok)
meta := s.getMetadata()
assert.Equal(t, "tx-span-tag", meta[ext.DSMTransactionID])
assert.Equal(t, "processed", meta[ext.DSMTransactionCheckpoint])
v, _ := s.meta.Get(ext.DSMTransactionID)
assert.Equal(t, "tx-span-tag", v)
v, _ = s.meta.Get(ext.DSMTransactionCheckpoint)
assert.Equal(t, "processed", v)
}

// TestTrackDataStreamsTransactionNoSpanInContextNoops verifies that when the context
Expand Down Expand Up @@ -95,7 +96,8 @@ func TestTrackDataStreamsTransactionAtTagsSpan(t *testing.T) {

s, ok := SpanFromContext(ctx)
require.True(t, ok)
meta := s.getMetadata()
assert.Equal(t, "tx-at-span", meta[ext.DSMTransactionID])
assert.Equal(t, "delivered", meta[ext.DSMTransactionCheckpoint])
v, _ := s.meta.Get(ext.DSMTransactionID)
assert.Equal(t, "tx-at-span", v)
v, _ = s.meta.Get(ext.DSMTransactionCheckpoint)
assert.Equal(t, "delivered", v)
}
193 changes: 193 additions & 0 deletions ddtrace/tracer/internal/span_attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016 Datadog, Inc.

package internal

import (
"iter"
"math/bits"
"unsafe"
)

// AttrKey is an integer index into a SpanAttributes value array.
// Use the pre-declared constants; do not construct AttrKey from arbitrary integers.
type AttrKey uint8

const (
AttrEnv AttrKey = 0
AttrVersion AttrKey = 1
AttrLanguage AttrKey = 2
numAttrs AttrKey = 3

// AttrUnknown is returned by AttrKeyForTag when no promoted tag matches.
// Its value is intentionally out of range for vals[] so misuse panics immediately.
AttrUnknown AttrKey = 0xFF
)

// Compile-time guard: the numeric values of AttrKey constants are load-bearing —
// they index both vals[] and setMask bit positions. If any value drifts (e.g. via
// iota + reorder), the expression below produces a compile error.
var (
_ = [1]byte{}[AttrEnv] // AttrEnv must be 0
_ = [1]byte{}[AttrVersion-1] // AttrVersion must be 1
_ = [1]byte{}[AttrLanguage-2] // AttrLanguage must be 2
_ = [1]byte{}[numAttrs-3] // numAttrs must be 3 (update when adding attributes)
)

// SpanAttributes holds the V1-protocol promoted span fields.
// Zero value = all fields absent.
// Set(key, "") is distinct from never-Set: the bit is set, the string is "".
//
// Layout: 1-byte setMask + 1-byte readOnly + 6B padding + [3]string (48B) = 56 bytes.
//
// When readOnly is true, the instance is owned by the tracer and must not be
// mutated. Callers must Clone before writing (copy-on-write).
type SpanAttributes struct {
setMask uint8
readOnly bool
vals [numAttrs]string
}

// Compile-time layout check: SpanAttributes must be exactly 56 bytes.
// 1B setMask + 1B readOnly + 6B padding + [3]string (48B) = 56B.
var _ = [1]byte{}[56-unsafe.Sizeof(SpanAttributes{})]
var _ = [1]byte{}[unsafe.Sizeof(SpanAttributes{})-56]

// All read methods are nil-safe so callers holding a *SpanAttributes don't
// need nil guards.

func (a *SpanAttributes) Set(key AttrKey, v string) {
if a == nil {
return
}
a.vals[key] = v
a.setMask |= 1 << key
}

// Unset clears the attribute for key, making it absent (as if never set). nil-safe.
func (a *SpanAttributes) Unset(key AttrKey) {
if a == nil {
return
}
a.vals[key] = ""
a.setMask &^= 1 << key
}

func (a *SpanAttributes) Val(key AttrKey) string {
if a == nil {
return ""
}
return a.vals[key]
}

func (a *SpanAttributes) Has(key AttrKey) bool {
return a != nil && a.setMask>>key&1 != 0
}

func (a *SpanAttributes) Get(key AttrKey) (string, bool) {
return a.Val(key), a.Has(key)
}

// Count returns the number of promoted fields that have been set.
func (a *SpanAttributes) Count() int {
if a == nil {
return 0
}
return bits.OnesCount8(a.setMask)
}

// MarkReadOnly marks this instance as readOnly (read-only). Clone before mutating.
// The receiver must be non-nil; marking a nil SpanAttributes read-only is a
// programming error and panics.
func (a *SpanAttributes) MarkReadOnly() { a.readOnly = true }

// IsReadOnly reports whether this is a readOnly instance requiring COW.
func (a *SpanAttributes) IsReadOnly() bool { return a != nil && a.readOnly }

// Reset clears all set attributes, returning the instance to its zero state.
// It is nil-safe and does not free the underlying memory, making it suitable
// for reuse (e.g. in a decode loop that reuses Span objects).
func (a *SpanAttributes) Reset() {
if a == nil {
return
}
*a = SpanAttributes{}
}

// Clone returns a mutable (non-readOnly) shallow copy.
func (a *SpanAttributes) Clone() *SpanAttributes {
if a == nil {
return &SpanAttributes{}
}
cp := *a
cp.readOnly = false
return &cp
}

// AttrDef maps an AttrKey to its canonical tag name.
type AttrDef struct {
Key AttrKey
Name string
}

// Defs enumerates all promoted attribute definitions.
var Defs = [numAttrs]AttrDef{
{AttrEnv, "env"},
{AttrVersion, "version"},
{AttrLanguage, "language"},
}

// IsPromotedKeyLen reports whether n matches the length of any promoted attribute name.
// Promoted keys: "env"(3), "version"(7), "language"(8).
// This must stay in sync with the Defs table; the init check below enforces
// this at program start.
func IsPromotedKeyLen(n int) bool {
switch n {
case 3, 7, 8:
return true
}
return false
}

func init() {
for _, d := range Defs {
if !IsPromotedKeyLen(len(d.Name)) {
panic("IsPromotedKeyLen out of sync with Defs: missing length " + d.Name)
}
}
}

// all returns an iterator over the set attributes (name, value) pairs.
func (a *SpanAttributes) all() iter.Seq2[string, string] {
return func(yield func(string, string) bool) {
if a == nil {
return
}
for _, d := range Defs {
if a.Has(d.Key) {
if !yield(d.Name, a.vals[d.Key]) {
return
}
}
}
}
}

// AttrKeyForTag returns the AttrKey for a promoted tag name, if any.
// Returns (AttrUnknown, false) when the tag is not a promoted attribute.
func AttrKeyForTag(tag string) (AttrKey, bool) {
if !IsPromotedKeyLen(len(tag)) {
return AttrUnknown, false
}
switch tag {
case "env":
return AttrEnv, true
case "version":
return AttrVersion, true
case "language":
return AttrLanguage, true
}
return AttrUnknown, false
}
Loading
Loading