diff --git a/ddtrace/tracer/idx/go.sum b/ddtrace/tracer/idx/go.sum new file mode 100644 index 0000000000..2ba3aff6eb --- /dev/null +++ b/ddtrace/tracer/idx/go.sum @@ -0,0 +1,4 @@ +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= +github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= diff --git a/ddtrace/tracer/idx/internal_span.go b/ddtrace/tracer/idx/internal_span.go new file mode 100644 index 0000000000..e0f21a0fe7 --- /dev/null +++ b/ddtrace/tracer/idx/internal_span.go @@ -0,0 +1,1157 @@ +// 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-present Datadog, Inc. + +package idx + +import ( + "encoding/binary" + "fmt" + "maps" + "strconv" + "strings" + + "github.com/tinylib/msgp/msgp" +) + +// StringTable is a table of strings that is used to store the de-duplicated strings in a trace +// Strings are not garbage collected automatically, so it is important to call RemoveUnusedStrings +// on the tracer payload to remove any strings that are no longer referenced. +type StringTable struct { + strings []string + lookup map[string]uint32 +} + +// NewStringTable creates a new string table, always starts with an empty string at index 0 +func NewStringTable() *StringTable { + return &StringTable{ + strings: []string{""}, + lookup: map[string]uint32{"": 0}, + } +} + +// StringTableFromArray creates a new string table from an array of already de-duplicated strings +func StringTableFromArray(strings []string) *StringTable { + st := &StringTable{ + strings: make([]string, len(strings)), + lookup: make(map[string]uint32, len(strings)), + } + for i, str := range strings { + st.strings[i+1] = str + st.lookup[str] = uint32(i + 1) + } + return st +} + +// Msgsize returns the size of the message when serialized. +func (s *StringTable) Msgsize() int { + size := 0 + size += msgp.ArrayHeaderSize + size += msgp.StringPrefixSize * len(s.strings) + for _, str := range s.strings { + size += len(str) + } + return size +} + +// Clone creates a deep copy of the string table. +func (s *StringTable) Clone() *StringTable { + clone := &StringTable{ + strings: append([]string{}, s.strings...), + lookup: make(map[string]uint32, len(s.lookup)), + } + maps.Copy(clone.lookup, s.lookup) + return clone +} + +// addUnchecked adds a string to the string table without checking for duplicates +func (s *StringTable) addUnchecked(str string) uint32 { + s.strings = append(s.strings, str) + s.lookup[str] = uint32(len(s.strings) - 1) + return uint32(len(s.strings) - 1) +} + +// Add adds a string to the string table if it doesn't already exist and returns the index of the string +func (s *StringTable) Add(str string) uint32 { + if idx, ok := s.lookup[str]; ok { + return idx + } + return s.addUnchecked(str) +} + +// Get returns the string at the given index - panics if out of bounds +func (s *StringTable) Get(idx uint32) string { + return s.strings[idx] +} + +// Len returns the number of strings in the string table +func (s *StringTable) Len() int { + return len(s.strings) +} + +// Lookup returns the index of the string in the string table, or 0 if the string is not found +func (s *StringTable) Lookup(str string) uint32 { + if idx, ok := s.lookup[str]; ok { + return idx + } + return 0 +} + +// InternalTracerPayload is a tracer payload structure that is optimized for trace-agent usage +// Namely it stores Attributes as a map for fast key lookups. +type InternalTracerPayload struct { + // Strings referenced in this tracer payload, its chunks and spans + // This should generally not be accessed directly, but rather through the methods on the InternalTracerPayload + // It is only exposed here for use in other packages that need to construct tracer payloads for testing. + Strings *StringTable + // containerID specifies the ref in the strings table of the ID of the container where the tracer is running on. + containerIDRef uint32 + // languageName specifies the ref in the strings table of the language of the tracer. + languageNameRef uint32 + // languageVersion specifies the ref in the strings table of the language version of the tracer. + languageVersionRef uint32 + // tracerVersion specifies the ref in the strings table of the version of the tracer. + tracerVersionRef uint32 + // runtimeID specifies the ref in the strings table of the V4 UUID representation of a tracer session. + runtimeIDRef uint32 + // env specifies the ref in the strings table of the `env` tag that set with the tracer. + envRef uint32 + // hostname specifies the ref in the strings table of the hostname of where the tracer is running. + hostnameRef uint32 + // version specifies the ref in the strings table of the `version` tag that set with the tracer. + appVersionRef uint32 + // a collection of key to value pairs common in all `chunks` + Attributes map[uint32]*AnyValue + // chunks specifies list of containing trace chunks. + Chunks []*InternalTraceChunk +} + +// Msgsize returns the size of the message when serialized. +func (tp *InternalTracerPayload) Msgsize() int { + size := 0 + size += tp.Strings.Msgsize() + size += msgp.Uint32Size + msgp.Uint32Size // containerIDRef + size += msgp.Uint32Size + msgp.Uint32Size // languageNameRef + size += msgp.Uint32Size + msgp.Uint32Size // languageVersionRef + size += msgp.Uint32Size + msgp.Uint32Size // tracerVersionRef + size += msgp.Uint32Size + msgp.Uint32Size // runtimeIDRef + size += msgp.Uint32Size + msgp.Uint32Size // envRef + size += msgp.Uint32Size + msgp.Uint32Size // hostnameRef + size += msgp.Uint32Size + msgp.Uint32Size // appVersionRef + size += msgp.MapHeaderSize // Attributes + for _, attr := range tp.Attributes { + size += msgp.Uint32Size + attr.Msgsize() // Key size + Attribute size + } + size += msgp.ArrayHeaderSize // Chunks + for _, chunk := range tp.Chunks { + size += chunk.Msgsize() + } + return size +} + +// RemoveUnusedStrings removes any strings from the string table that are not referenced in the tracer payload +// This should be called before marshalling or otherwise exposing the tracer payload to remove any sensitive +// strings that are no longer referenced +func (tp *InternalTracerPayload) RemoveUnusedStrings() { + usedStrings := make([]bool, tp.Strings.Len()) + usedStrings[tp.containerIDRef] = true + usedStrings[tp.languageNameRef] = true + usedStrings[tp.languageVersionRef] = true + usedStrings[tp.tracerVersionRef] = true + usedStrings[tp.runtimeIDRef] = true + usedStrings[tp.envRef] = true + usedStrings[tp.hostnameRef] = true + usedStrings[tp.appVersionRef] = true + markAttributeMapStringsUsed(usedStrings, tp.Strings, tp.Attributes) + for _, chunk := range tp.Chunks { + chunk.markUsedStrings(usedStrings) + } + for i, used := range usedStrings { + if !used { + // Remove the reverse lookup and set the string to the empty string + // We don't adjust the table itself to avoid changing the indices of the other strings + delete(tp.Strings.lookup, tp.Strings.strings[i]) + tp.Strings.strings[i] = "" + } + } +} + +// SetAttributes sets the attributes for the tracer payload. +func (tp *InternalTracerPayload) SetAttributes(attributes map[uint32]*AnyValue) { + tp.Attributes = attributes +} + +// Hostname returns the hostname from the tracer payload. +func (tp *InternalTracerPayload) Hostname() string { + return tp.Strings.Get(tp.hostnameRef) +} + +// SetHostname sets the hostname for the tracer payload. +func (tp *InternalTracerPayload) SetHostname(hostname string) { + tp.hostnameRef = tp.Strings.Add(hostname) +} + +// AppVersion returns the application version from the tracer payload. +func (tp *InternalTracerPayload) AppVersion() string { + return tp.Strings.Get(tp.appVersionRef) +} + +// SetAppVersion sets the application version for the tracer payload. +func (tp *InternalTracerPayload) SetAppVersion(version string) { + tp.appVersionRef = tp.Strings.Add(version) +} + +// LanguageName returns the language name from the tracer payload. +func (tp *InternalTracerPayload) LanguageName() string { + return tp.Strings.Get(tp.languageNameRef) +} + +// SetLanguageName sets the language name in the string table +func (tp *InternalTracerPayload) SetLanguageName(name string) { + tp.languageNameRef = tp.Strings.Add(name) +} + +// LanguageVersion returns the language version from the tracer payload. +func (tp *InternalTracerPayload) LanguageVersion() string { + return tp.Strings.Get(tp.languageVersionRef) +} + +// SetLanguageVersion sets the language version in the string table +func (tp *InternalTracerPayload) SetLanguageVersion(version string) { + tp.languageVersionRef = tp.Strings.Add(version) +} + +// TracerVersion returns the tracer version from the tracer payload. +func (tp *InternalTracerPayload) TracerVersion() string { + return tp.Strings.Get(tp.tracerVersionRef) +} + +// SetTracerVersion sets the tracer version in the string table +func (tp *InternalTracerPayload) SetTracerVersion(version string) { + tp.tracerVersionRef = tp.Strings.Add(version) +} + +// ContainerID returns the container ID from the tracer payload. +func (tp *InternalTracerPayload) ContainerID() string { + return tp.Strings.Get(tp.containerIDRef) +} + +// SetContainerID sets the container ID for the tracer payload. +func (tp *InternalTracerPayload) SetContainerID(containerID string) { + tp.containerIDRef = tp.Strings.Add(containerID) +} + +// Env returns the environment from the tracer payload. +func (tp *InternalTracerPayload) Env() string { + return tp.Strings.Get(tp.envRef) +} + +// SetEnv sets the environment for the tracer payload. +func (tp *InternalTracerPayload) SetEnv(env string) { + tp.envRef = tp.Strings.Add(env) +} + +// RuntimeID returns the runtime ID from the tracer payload. +func (tp *InternalTracerPayload) RuntimeID() string { + return tp.Strings.Get(tp.runtimeIDRef) +} + +// SetRuntimeID sets the runtime ID for the tracer payload. +func (tp *InternalTracerPayload) SetRuntimeID(runtimeID string) { + tp.runtimeIDRef = tp.Strings.Add(runtimeID) +} + +// RemoveChunk removes a chunk by its index. +func (tp *InternalTracerPayload) RemoveChunk(i int) { + if i < 0 || i >= len(tp.Chunks) { + return + } + tp.Chunks[i] = tp.Chunks[len(tp.Chunks)-1] + tp.Chunks = tp.Chunks[:len(tp.Chunks)-1] +} + +// ReplaceChunk replaces a chunk by its index. +func (tp *InternalTracerPayload) ReplaceChunk(i int, chunk *InternalTraceChunk) { + tp.Chunks[i] = chunk +} + +// SetStringAttribute sets a string attribute for the tracer payload. +func (tp *InternalTracerPayload) SetStringAttribute(key, value string) { + setStringAttribute(key, value, tp.Strings, tp.Attributes) +} + +// GetAttributeAsString gets a string attribute from the tracer payload. +func (tp *InternalTracerPayload) GetAttributeAsString(key string) (string, bool) { + return getAttributeAsString(key, tp.Strings, tp.Attributes) +} + +// Cut cuts off a new tracer payload from the `p` with [0, i-1] chunks +// and keeps [i, n-1] chunks in the original payload `p`. +// The new payload will have a new string table, so it can be used concurrently with the original payload. +func (tp *InternalTracerPayload) Cut(i int) *InternalTracerPayload { + if i < 0 { + i = 0 + } + if i > len(tp.Chunks) { + i = len(tp.Chunks) + } + newStrings := tp.Strings.Clone() + newPayload := InternalTracerPayload{ + Strings: newStrings, // Clone string table to protect against concurrent access + containerIDRef: tp.containerIDRef, + languageNameRef: tp.languageNameRef, + languageVersionRef: tp.languageVersionRef, + tracerVersionRef: tp.tracerVersionRef, + runtimeIDRef: tp.runtimeIDRef, + envRef: tp.envRef, + hostnameRef: tp.hostnameRef, + appVersionRef: tp.appVersionRef, + Attributes: tp.Attributes, + } + newPayload.Chunks = tp.Chunks[:i] + for _, chunk := range newPayload.Chunks { + chunk.Strings = newStrings + for _, span := range chunk.Spans { + span.Strings = newStrings + } + } + tp.Chunks = tp.Chunks[i:] + return &newPayload +} + +// InternalTraceChunk is a trace chunk structure that is optimized for trace-agent usage +// Namely it stores Attributes as a map for fast key lookups and holds a pointer to the strings slice +// so a trace chunk holds all local context necessary to understand all fields +type InternalTraceChunk struct { + // Strings referenced in this trace chunk. Note this is shared with the tracer payload + // This should generally not be accessed directly, but rather through the methods on the InternalTracerPayload + // It is only exposed here for use in other packages that need to construct tracer payloads for testing. + Strings *StringTable + Priority int32 + originRef uint32 + Attributes map[uint32]*AnyValue + Spans []*InternalSpan + DroppedTrace bool + TraceID []byte + samplingMechanism uint32 +} + +// NewInternalTraceChunk creates a new internal trace chunk. +func NewInternalTraceChunk(strings *StringTable, priority int32, origin string, attributes map[uint32]*AnyValue, spans []*InternalSpan, droppedTrace bool, traceID []byte, samplingMechanism uint32) *InternalTraceChunk { + return &InternalTraceChunk{ + Strings: strings, + Priority: priority, + originRef: strings.Add(origin), + Attributes: attributes, + Spans: spans, + DroppedTrace: droppedTrace, + TraceID: traceID, + samplingMechanism: samplingMechanism, + } +} + +// ShallowCopy creates a shallow copy of the internal trace chunk. +// TODO: add a test to verify we have all fields +func (c *InternalTraceChunk) ShallowCopy() *InternalTraceChunk { + return &InternalTraceChunk{ + Strings: c.Strings, + Priority: c.Priority, + originRef: c.originRef, + Attributes: c.Attributes, + Spans: c.Spans, + DroppedTrace: c.DroppedTrace, + TraceID: c.TraceID, + samplingMechanism: c.samplingMechanism, + } +} + +// Msgsize returns the size of the message when serialized. +func (c *InternalTraceChunk) Msgsize() int { + size := 0 + size += c.Strings.Msgsize() + size += msgp.Int32Size // Priority + size += msgp.Uint32Size // OriginRef + size += msgp.MapHeaderSize // Attributes + for _, attr := range c.Attributes { + size += msgp.Uint32Size + attr.Msgsize() // Key size + Attribute size + } + size += msgp.ArrayHeaderSize // Spans + for _, span := range c.Spans { + size += span.Msgsize() + } + size += msgp.BoolSize // DroppedTrace + size += msgp.BytesPrefixSize + 16 // TraceID (128 bits) + size += msgp.Uint32Size // DecisionMakerRef + return size +} + +// LegacyTraceID returns the trace ID of the trace chunk as a uint64, the lowest order 8 bytes of the trace ID are the legacy trace ID +func (c *InternalTraceChunk) LegacyTraceID() uint64 { + return binary.BigEndian.Uint64(c.TraceID[8:]) +} + +// Origin returns the origin from the trace chunk. +func (c *InternalTraceChunk) Origin() string { + return c.Strings.Get(c.originRef) +} + +// SetOrigin sets the origin for the trace chunk. +func (c *InternalTraceChunk) SetOrigin(origin string) { + c.originRef = c.Strings.Add(origin) +} + +// SamplingMechanism returns the sampling mechanism from the trace chunk. +func (c *InternalTraceChunk) SamplingMechanism() uint32 { + return c.samplingMechanism +} + +// SetSamplingMechanism sets the sampling mechanism for the trace chunk. +func (c *InternalTraceChunk) SetSamplingMechanism(samplingMechanism uint32) { + c.samplingMechanism = samplingMechanism +} + +// GetAttributeAsString returns the attribute as a string, or an empty string if the attribute is not found +func (c *InternalTraceChunk) GetAttributeAsString(key string) (string, bool) { + return getAttributeAsString(key, c.Strings, c.Attributes) +} + +// SetStringAttribute sets a string attribute for the trace chunk. +func (c *InternalTraceChunk) SetStringAttribute(key, value string) { + setStringAttribute(key, value, c.Strings, c.Attributes) +} + +func (c *InternalTraceChunk) markUsedStrings(usedStrings []bool) { + usedStrings[c.originRef] = true + markAttributeMapStringsUsed(usedStrings, c.Strings, c.Attributes) + for _, span := range c.Spans { + span.markUsedStrings(usedStrings) + } +} + +// InternalSpan is a span structure that is optimized for trace-agent usage +// Namely it stores Attributes as a map for fast key lookups and holds a pointer to the strings slice +// so a span holds all local context necessary to understand all fields +type InternalSpan struct { + // Strings referenced in this span. Note this is shared with the tracer payload + // This should generally not be accessed directly, but rather through the methods on the InternalTracerPayload + // It is only exposed here for use in other packages that need to construct tracer payloads for testing. + Strings *StringTable + span *Span // We reference the proto span directly to avoid the allocation overhead when converting this to a proto span +} + +// NewInternalSpan creates a new internal span. +func NewInternalSpan(strings *StringTable, span *Span) *InternalSpan { + return &InternalSpan{ + Strings: strings, + span: span, + } +} + +// ShallowCopy creates a shallow copy of the internal span. +func (s *InternalSpan) ShallowCopy() *InternalSpan { + return &InternalSpan{ + Strings: s.Strings, + span: s.span.ShallowCopy(), + } +} + +func (s *InternalSpan) markUsedStrings(usedStrings []bool) { + usedStrings[s.span.ServiceRef] = true + usedStrings[s.span.NameRef] = true + usedStrings[s.span.ResourceRef] = true + usedStrings[s.span.TypeRef] = true + usedStrings[s.span.EnvRef] = true + usedStrings[s.span.VersionRef] = true + usedStrings[s.span.ComponentRef] = true + markAttributeMapStringsUsed(usedStrings, s.Strings, s.span.Attributes) + for _, link := range s.span.Links { + markSpanLinkUsedStrings(usedStrings, s.Strings, link) + } + for _, event := range s.span.Events { + markSpanEventUsedStrings(usedStrings, s.Strings, event) + } +} + +// ToProto converts the internal span to a protobuf span. +func (s *InternalSpan) ToProto() *Span { + return s.span +} + +func markSpanLinkUsedStrings(usedStrings []bool, strTable *StringTable, link *SpanLink) { + usedStrings[link.TracestateRef] = true + markAttributeMapStringsUsed(usedStrings, strTable, link.Attributes) +} + +func markSpanEventUsedStrings(usedStrings []bool, strTable *StringTable, event *SpanEvent) { + usedStrings[event.NameRef] = true + markAttributeMapStringsUsed(usedStrings, strTable, event.Attributes) +} + +// ShallowCopy returns a shallow copy of the span +func (s *Span) ShallowCopy() *Span { + return &Span{ + ServiceRef: s.ServiceRef, + NameRef: s.NameRef, + ResourceRef: s.ResourceRef, + SpanID: s.SpanID, + ParentID: s.ParentID, + Start: s.Start, + Duration: s.Duration, + Error: s.Error, + Attributes: s.Attributes, + TypeRef: s.TypeRef, + Links: s.Links, + Events: s.Events, + EnvRef: s.EnvRef, + VersionRef: s.VersionRef, + ComponentRef: s.ComponentRef, + Kind: s.Kind, + } +} + +// DebugString returns a human readable string representation of the span +func (s *InternalSpan) DebugString() string { + str := "Span {" + str += fmt.Sprintf("Service: (%s, at %d), ", s.Service(), s.span.ServiceRef) + str += fmt.Sprintf("Name: (%s, at %d), ", s.Name(), s.span.NameRef) + str += fmt.Sprintf("Resource: (%s, at %d), ", s.Resource(), s.span.ResourceRef) + str += fmt.Sprintf("SpanID: %d, ", s.span.SpanID) + str += fmt.Sprintf("ParentID: %d, ", s.span.ParentID) + str += fmt.Sprintf("Start: %d, ", s.span.Start) + str += fmt.Sprintf("Duration: %d, ", s.span.Duration) + str += fmt.Sprintf("Error: %t, ", s.span.Error) + str += fmt.Sprintf("Attributes: %v, ", s.span.Attributes) + str += fmt.Sprintf("Type: (%s, at %d), ", s.Type(), s.span.TypeRef) + str += fmt.Sprintf("Links: %v, ", s.Links()) + str += fmt.Sprintf("Events: %v, ", s.Events()) + str += fmt.Sprintf("Env: (%s, at %d), ", s.Env(), s.span.EnvRef) + str += fmt.Sprintf("Version: (%s, at %d), ", s.Version(), s.span.VersionRef) + str += fmt.Sprintf("Component: (%s, at %d), ", s.Component(), s.span.ComponentRef) + str += fmt.Sprintf("Kind: %s, ", s.SpanKind()) + str += "}" + return str +} + +// Events returns the spans events in the InternalSpanEvent format +func (s *InternalSpan) Events() []*InternalSpanEvent { + events := make([]*InternalSpanEvent, len(s.span.Events)) + for i, event := range s.span.Events { + events[i] = &InternalSpanEvent{ + Strings: s.Strings, + event: event, + } + } + return events +} + +// Links returns the spans links in the InternalSpanLink format +func (s *InternalSpan) Links() []*InternalSpanLink { + links := make([]*InternalSpanLink, len(s.span.Links)) + for i, link := range s.span.Links { + links[i] = &InternalSpanLink{ + Strings: s.Strings, + link: link, + } + } + return links +} + +// LenLinks returns the number of links in the span. +func (s *InternalSpan) LenLinks() int { + return len(s.span.Links) +} + +// Msgsize returns the size of the message when serialized. +func (s *InternalSpan) Msgsize() int { + size := 0 + size += msgp.MapHeaderSize // Header (All fields are key-value pairs, uint32 for keys) + size += msgp.Uint32Size + msgp.Uint32Size // ServiceRef + size += msgp.Uint32Size + msgp.Uint32Size // NameRef + size += msgp.Uint32Size + msgp.Uint32Size // ResourceRef + size += msgp.Uint32Size + msgp.Uint64Size // SpanID + size += msgp.Uint32Size + msgp.Uint64Size // ParentID + size += msgp.Uint32Size + msgp.Uint64Size // Start + size += msgp.Uint32Size + msgp.Uint64Size // Duration + size += msgp.Uint32Size + msgp.BoolSize // Error + size += msgp.Uint32Size + msgp.MapHeaderSize // Attributes + for _, attr := range s.span.Attributes { + size += msgp.Uint32Size + attr.Msgsize() // Key size + Attribute size + } + size += msgp.Uint32Size + msgp.Uint32Size // TypeRef + size += msgp.Uint32Size + msgp.ArrayHeaderSize // SpanLinks + for _, link := range s.span.Links { + size += link.Msgsize() + } + size += msgp.Uint32Size + msgp.ArrayHeaderSize // SpanEvents + for _, event := range s.span.Events { + size += event.Msgsize() + } + size += msgp.Uint32Size + msgp.Uint32Size // EnvRef + size += msgp.Uint32Size + msgp.Uint32Size // VersionRef + size += msgp.Uint32Size + msgp.Uint32Size // ComponentRef + size += msgp.Uint32Size + msgp.Uint32Size // Kind + return size +} + +// SpanKind returns the string representation of the span kind +func (s *InternalSpan) SpanKind() string { + switch s.span.Kind { + case SpanKind_SPAN_KIND_INTERNAL: + return "internal" + case SpanKind_SPAN_KIND_SERVER: + return "server" + case SpanKind_SPAN_KIND_CLIENT: + return "client" + case SpanKind_SPAN_KIND_PRODUCER: + return "producer" + case SpanKind_SPAN_KIND_CONSUMER: + return "consumer" + default: + return "unknown" + } +} + +// Service returns the service name from the span. +func (s *InternalSpan) Service() string { + return s.Strings.Get(s.span.ServiceRef) +} + +// SetService sets the service name for the span. +func (s *InternalSpan) SetService(svc string) { + s.span.ServiceRef = s.Strings.Add(svc) +} + +// Name returns the span name. +func (s *InternalSpan) Name() string { + return s.Strings.Get(s.span.NameRef) +} + +// SetName sets the span name. +func (s *InternalSpan) SetName(name string) { + s.span.NameRef = s.Strings.Add(name) +} + +// Resource returns the resource from the span. +func (s *InternalSpan) Resource() string { + return s.Strings.Get(s.span.ResourceRef) +} + +// SetResource sets the resource for the span. +func (s *InternalSpan) SetResource(resource string) { + s.span.ResourceRef = s.Strings.Add(resource) +} + +// Type returns the span type. +func (s *InternalSpan) Type() string { + return s.Strings.Get(s.span.TypeRef) +} + +// SetType sets the span type. +func (s *InternalSpan) SetType(t string) { + s.span.TypeRef = s.Strings.Add(t) +} + +// Env returns the environment from the span. +func (s *InternalSpan) Env() string { + return s.Strings.Get(s.span.EnvRef) +} + +// SetEnv sets the environment for the span. +func (s *InternalSpan) SetEnv(e string) { + s.span.EnvRef = s.Strings.Add(e) +} + +// ParentID returns the parent span ID. +func (s *InternalSpan) ParentID() uint64 { + return s.span.ParentID +} + +// SetParentID sets the parent span ID. +func (s *InternalSpan) SetParentID(parentID uint64) { + s.span.ParentID = parentID +} + +// SpanID returns the span ID. +func (s *InternalSpan) SpanID() uint64 { + return s.span.SpanID +} + +// SetSpanID sets the span ID. +func (s *InternalSpan) SetSpanID(spanID uint64) { + s.span.SpanID = spanID +} + +// Start returns the start time of the span. +func (s *InternalSpan) Start() uint64 { + return s.span.Start +} + +// SetStart sets the start time of the span. +func (s *InternalSpan) SetStart(start uint64) { + s.span.Start = start +} + +func (s *InternalSpan) Error() bool { + return s.span.Error +} + +// SetError sets the error flag for the span. +func (s *InternalSpan) SetError(error bool) { + s.span.Error = error +} + +// Attributes returns the attributes of the span. +func (s *InternalSpan) Attributes() map[uint32]*AnyValue { + return s.span.Attributes +} + +// Duration returns the duration of the span. +func (s *InternalSpan) Duration() uint64 { + return s.span.Duration +} + +// SetDuration sets the duration of the span. +func (s *InternalSpan) SetDuration(duration uint64) { + s.span.Duration = duration +} + +// Kind returns the span kind. +func (s *InternalSpan) Kind() SpanKind { + return s.span.Kind +} + +// SetSpanKind sets the span kind. +func (s *InternalSpan) SetSpanKind(kind SpanKind) { + s.span.Kind = kind +} + +// Component returns the component from the span. +func (s *InternalSpan) Component() string { + return s.Strings.Get(s.span.ComponentRef) +} + +// SetComponent sets the component for the span. +func (s *InternalSpan) SetComponent(component string) { + s.span.ComponentRef = s.Strings.Add(component) +} + +// Version returns the version from the span. +func (s *InternalSpan) Version() string { + return s.Strings.Get(s.span.VersionRef) +} + +// SetVersion sets the version for the span. +func (s *InternalSpan) SetVersion(version string) { + s.span.VersionRef = s.Strings.Add(version) +} + +// GetAttributeAsString returns the attribute as a string, or an empty string if the attribute is not found +func (s *InternalSpan) GetAttributeAsString(key string) (string, bool) { + // To maintain backwards compatibility, we need to handle some special cases where these keys used to be set as tags + if key == "env" { + return s.Env(), true + } + if key == "version" { + return s.Version(), true + } + if key == "component" { + return s.Component(), true + } + if key == "span.kind" { + return s.SpanKind(), true + } + return getAttributeAsString(key, s.Strings, s.span.Attributes) +} + +// GetAttributeAsFloat64 returns the attribute as a float64 and a boolean indicating if the attribute was found AND it was able to be converted to a float64 +func (s *InternalSpan) GetAttributeAsFloat64(key string) (float64, bool) { + keyIdx := s.Strings.Lookup(key) + if keyIdx == 0 { + return 0, false + } + if attr, ok := s.span.Attributes[keyIdx]; ok { + doubleVal, err := attr.AsDoubleValue(s.Strings) + if err != nil { + return 0, false + } + return doubleVal, true + } + return 0, false +} + +// SetStringAttribute sets the attribute with key and value +// For backwards compatibility, env, version, component, and span.kind will be set as fields instead of attributes +func (s *InternalSpan) SetStringAttribute(key, value string) { + if s.span.Attributes == nil { + s.span.Attributes = make(map[uint32]*AnyValue) + } + if s.setCompatibleTags(key, value) { + return + } + setStringAttribute(key, value, s.Strings, s.span.Attributes) +} + +// SetFloat64Attribute sets a float64 attribute for the span. +func (s *InternalSpan) SetFloat64Attribute(key string, value float64) { + if s.span.Attributes == nil { + s.span.Attributes = make(map[uint32]*AnyValue) + } + setFloat64Attribute(key, value, s.Strings, s.span.Attributes) +} + +// setCompatibleTags checks if the key is a special case that was previously a tag +// if it is, then we set the new field and return true, otherwise we return false +func (s *InternalSpan) setCompatibleTags(key, value string) bool { + if s.span.Attributes == nil { + s.span.Attributes = make(map[uint32]*AnyValue) + } + if key == "env" { + s.SetEnv(value) + return true + } + if key == "version" { + s.SetVersion(value) + return true + } + if key == "component" { + s.SetComponent(value) + return true + } + if key == "span.kind" { + newKind := SpanKind_SPAN_KIND_UNSPECIFIED + switch value { + case "internal": + newKind = SpanKind_SPAN_KIND_INTERNAL + case "server": + newKind = SpanKind_SPAN_KIND_SERVER + case "client": + newKind = SpanKind_SPAN_KIND_CLIENT + case "producer": + newKind = SpanKind_SPAN_KIND_PRODUCER + case "consumer": + newKind = SpanKind_SPAN_KIND_CONSUMER + } + if newKind == SpanKind_SPAN_KIND_UNSPECIFIED { + // On an unknown span kind, we just won't set it + return true + } + s.SetSpanKind(newKind) + return true + } + return false +} + +// SetAttributeFromString sets the attribute from a string, attempting to use the most backwards compatible type possible +// for the attribute value. Meaning we will prefer DoubleValue > IntValue > StringValue to match the previous metrics vs meta behavior +// For backwards compatibility, env, version, component, and span.kind will be set as fields instead of attributes +func (s *InternalSpan) SetAttributeFromString(key, value string) { + if s.span.Attributes == nil { + s.span.Attributes = make(map[uint32]*AnyValue) + } + if s.setCompatibleTags(key, value) { + return + } + setAttribute(key, FromString(s.Strings, value), s.Strings, s.span.Attributes) +} + +// DeleteAttribute deletes an attribute from the span. +func (s *InternalSpan) DeleteAttribute(key string) { + deleteAttribute(key, s.Strings, s.span.Attributes) +} + +// MapAttributesAsStrings maps over all string attributes and applies the given function to each attribute +// Note that this will only act on true attributes, fields like env, version, component, etc are not considered +// The provided function will receive all attributes as strings, and should return the new value for the attribute +func (s *InternalSpan) MapAttributesAsStrings(f func(k, v string) string) { + for k, v := range s.span.Attributes { + // TODO: we could cache the results of these transformations + // TODO: This is only used for CC obfuscation today, we could optimize this to reduce the overhead here + vString := v.AsString(s.Strings) + newV := f(s.Strings.Get(k), vString) + if newV != vString { + s.span.Attributes[k] = &AnyValue{ + Value: &AnyValue_StringValueRef{ + StringValueRef: s.Strings.Add(newV), + }, + } + } + } +} + +// MapStringAttributesFunc is a function that maps over all string attributes and applies the given function to each attribute +type MapStringAttributesFunc func(k, v string) (newK string, newV string, shouldReplace bool) + +// MapStringAttributes maps over all string attributes and applies the given function to each attribute +// Note that this will only act on true attributes, fields like env, version, component, etc are not considered +// The provided function will only act on attributes that are string types +func (s *InternalSpan) MapStringAttributes(f MapStringAttributesFunc) { + for k, v := range s.span.Attributes { + if vStrAttr, ok := v.Value.(*AnyValue_StringValueRef); ok { + oldK := s.Strings.Get(k) + oldV := s.Strings.Get(vStrAttr.StringValueRef) + newK, newV, shouldReplace := f(oldK, oldV) + if shouldReplace { + newVAttr := v + if newV != oldV { + newVAttr.Value.(*AnyValue_StringValueRef).StringValueRef = s.Strings.Add(newV) + } + kIdx := k + if newK != oldK { + // Key has changed we must introduce a new attribute + delete(s.span.Attributes, k) + kIdx = s.Strings.Add(newK) + } + s.span.Attributes[kIdx] = newVAttr + } + } + } +} + +// InternalSpanLink is a span link structure that is optimized for trace-agent usage +// Namely it stores Attributes as a map for fast key lookups +type InternalSpanLink struct { + // Strings is a pointer to the strings slice (Shared across a tracer payload) + Strings *StringTable + link *SpanLink +} + +// Msgsize returns the size of the message when serialized. +func (sl *SpanLink) Msgsize() int { + size := 0 + size += msgp.MapHeaderSize // Map + size += msgp.Uint32Size + msgp.BytesPrefixSize + 16 // TraceID (128 bits) + size += msgp.Uint32Size + msgp.Uint64Size // SpanID + size += msgp.Uint32Size + msgp.MapHeaderSize // Attributes + for _, attr := range sl.Attributes { + size += msgp.Uint32Size + attr.Msgsize() // Key size + Attribute size + } + size += msgp.Uint32Size + msgp.Uint32Size // TracestateRef + size += msgp.Uint32Size + msgp.Uint32Size // Flags + return size +} + +// TraceID returns the trace ID from the span link. +func (sl *InternalSpanLink) TraceID() []byte { + return sl.link.TraceID +} + +// SpanID returns the span ID from the span link. +func (sl *InternalSpanLink) SpanID() uint64 { + return sl.link.SpanID +} + +// Flags returns the flags from the span link. +func (sl *InternalSpanLink) Flags() uint32 { + return sl.link.Flags +} + +// GetAttributeAsString gets a string attribute from the span link. +func (sl *InternalSpanLink) GetAttributeAsString(key string) (string, bool) { + return getAttributeAsString(key, sl.Strings, sl.link.Attributes) +} + +// SetStringAttribute sets a string attribute for the span link. +func (sl *InternalSpanLink) SetStringAttribute(key, value string) { + setStringAttribute(key, value, sl.Strings, sl.link.Attributes) +} + +// Tracestate returns the tracestate from the span link. +func (sl *InternalSpanLink) Tracestate() string { + return sl.Strings.Get(sl.link.TracestateRef) +} + +// Attributes returns the attributes of the span link. +func (sl *InternalSpanLink) Attributes() map[uint32]*AnyValue { + return sl.link.Attributes +} + +// InternalSpanEvent is the canonical internal span event structure +type InternalSpanEvent struct { + // Strings is a pointer to the strings slice (Shared across a tracer payload) + Strings *StringTable + event *SpanEvent +} + +// Name returns the name from the span event. +func (se *InternalSpanEvent) Name() string { + return se.Strings.Get(se.event.NameRef) +} + +// Attributes returns the attributes of the span event. +func (se *InternalSpanEvent) Attributes() map[uint32]*AnyValue { + return se.event.Attributes +} + +// Msgsize returns the size of the message when serialized. +func (se *SpanEvent) Msgsize() int { + size := 0 + size += msgp.MapHeaderSize // Map + size += msgp.Uint32Size + msgp.Uint64Size // Time + size += msgp.Uint32Size + msgp.Uint32Size // NameRef + size += msgp.Uint32Size + msgp.MapHeaderSize // Attributes + for _, attr := range se.Attributes { + size += msgp.Uint32Size + attr.Msgsize() // Key size + Attribute size + } + return size +} + +// GetAttributeAsString gets a string attribute from the span event. +func (se *InternalSpanEvent) GetAttributeAsString(key string) (string, bool) { + return getAttributeAsString(key, se.Strings, se.event.Attributes) +} + +// SetAttributeFromString sets the attribute on an InternalSpanEvent from a string, attempting to use the most backwards compatible type possible +// for the attribute value. Meaning we will prefer DoubleValue > IntValue > StringValue to match the previous metrics vs meta behavior +func (se *InternalSpanEvent) SetAttributeFromString(key, value string) { + setAttribute(key, FromString(se.Strings, value), se.Strings, se.event.Attributes) +} + +// AsString returns the attribute in string format, this format is backwards compatible with non-v1 behavior +func (attr *AnyValue) AsString(strTable *StringTable) string { + switch v := attr.Value.(type) { + case *AnyValue_StringValueRef: + return strTable.Get(v.StringValueRef) + case *AnyValue_BoolValue: + return strconv.FormatBool(v.BoolValue) + case *AnyValue_DoubleValue: + return strconv.FormatFloat(v.DoubleValue, 'f', -1, 64) + case *AnyValue_IntValue: + return strconv.FormatInt(v.IntValue, 10) + case *AnyValue_BytesValue: + return string(v.BytesValue) + case *AnyValue_ArrayValue: + values := v.ArrayValue.Values + valuesStr := []string{} + for _, value := range values { + valuesStr = append(valuesStr, value.AsString(strTable)) + } + return "[" + strings.Join(valuesStr, ",") + "]" + case *AnyValue_KeyValueList: + values := v.KeyValueList.KeyValues + valuesStr := []string{} + for _, kv := range values { + valuesStr = append(valuesStr, strTable.Get(kv.Key)+"="+kv.Value.AsString(strTable)) + } + return "{" + strings.Join(valuesStr, ",") + "}" + default: + return "" + } +} + +// AsDoubleValue returns the attribute in float64 format, returning an error if the attribute is not a float64 or can't be converted to a float64 +func (attr *AnyValue) AsDoubleValue(strTable *StringTable) (float64, error) { + switch v := attr.Value.(type) { + case *AnyValue_StringValueRef: + doubleVal, err := strconv.ParseFloat(strTable.Get(v.StringValueRef), 64) + if err != nil { + return 0, fmt.Errorf("string value not a float64: %w", err) + } + return doubleVal, nil + case *AnyValue_BoolValue: + if v.BoolValue { + return 1, nil + } + return 0, nil + case *AnyValue_DoubleValue: + return v.DoubleValue, nil + case *AnyValue_IntValue: + return float64(v.IntValue), nil + case *AnyValue_BytesValue: + return 0, fmt.Errorf("bytes value not a float64") + case *AnyValue_ArrayValue: + return 0, fmt.Errorf("array value not a float64") + case *AnyValue_KeyValueList: + return 0, fmt.Errorf("key-value list value not a float64") + default: + return 0, fmt.Errorf("unknown value type not a float64") + } +} + +// FromString creates an AnyValue from a string, attempting to use the most backwards compatible type possible +// Meaning we will prefer DoubleValue > IntValue > StringValue to match the previous metrics vs meta behavior +func FromString(strTable *StringTable, s string) *AnyValue { + if intVal, err := strconv.ParseInt(s, 10, 64); err == nil { + return &AnyValue{ + Value: &AnyValue_IntValue{ + IntValue: intVal, + }, + } + } + if floatVal, err := strconv.ParseFloat(s, 64); err == nil { + return &AnyValue{ + Value: &AnyValue_DoubleValue{ + DoubleValue: floatVal, + }, + } + } + return &AnyValue{ + Value: &AnyValue_StringValueRef{ + StringValueRef: strTable.Add(s), + }, + } +} + +func getAttributeAsString(key string, strTable *StringTable, attributes map[uint32]*AnyValue) (string, bool) { + keyIdx := strTable.Lookup(key) + if keyIdx == 0 { + return "", false + } + if attr, ok := attributes[keyIdx]; ok { + return attr.AsString(strTable), true + } + return "", false +} + +func setStringAttribute(key, value string, strTable *StringTable, attributes map[uint32]*AnyValue) { + setAttribute(key, &AnyValue{ + Value: &AnyValue_StringValueRef{ + StringValueRef: strTable.Add(value), + }, + }, strTable, attributes) +} + +func setFloat64Attribute(key string, value float64, strTable *StringTable, attributes map[uint32]*AnyValue) { + setAttribute(key, &AnyValue{ + Value: &AnyValue_DoubleValue{ + DoubleValue: value, + }, + }, strTable, attributes) +} + +func setAttribute(key string, value *AnyValue, strTable *StringTable, attributes map[uint32]*AnyValue) { + newKeyIdx := strTable.Add(key) + attributes[newKeyIdx] = value +} + +func deleteAttribute(key string, strTable *StringTable, attributes map[uint32]*AnyValue) { + keyIdx := strTable.Lookup(key) + if keyIdx != 0 { + delete(attributes, keyIdx) + } +} + +func markAttributeMapStringsUsed(usedStrings []bool, strTable *StringTable, attributes map[uint32]*AnyValue) { + for keyIdx, attr := range attributes { + usedStrings[keyIdx] = true + markAttributeStringUsed(usedStrings, strTable, attr) + } +} + +// markAttributeStringUsed marks the string referenced by the value as used +// This is used to track which strings are used in the span and can be removed from the string table +func markAttributeStringUsed(usedStrings []bool, strTable *StringTable, value *AnyValue) { + switch v := value.Value.(type) { + case *AnyValue_StringValueRef: + usedStrings[v.StringValueRef] = true + case *AnyValue_ArrayValue: + for _, value := range v.ArrayValue.Values { + markAttributeStringUsed(usedStrings, strTable, value) + } + case *AnyValue_KeyValueList: + for _, kv := range v.KeyValueList.KeyValues { + usedStrings[kv.Key] = true + markAttributeStringUsed(usedStrings, strTable, kv.Value) + } + } +} diff --git a/ddtrace/tracer/idx/span.go b/ddtrace/tracer/idx/span.go new file mode 100644 index 0000000000..3d491f296b --- /dev/null +++ b/ddtrace/tracer/idx/span.go @@ -0,0 +1,838 @@ +// 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-present Datadog, Inc. + +// Package idx is used to unmarshal v1.0 Trace payloads +package idx + +import ( + "fmt" + + "github.com/tinylib/msgp/msgp" +) + +// UnmarshalSpanList unmarshals a list of InternalSpans from a byte stream, updating the strings slice with new strings +func UnmarshalSpanList(bts []byte, strings *StringTable) (spans []*InternalSpan, o []byte, err error) { + var numSpans uint32 + numSpans, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span list header") + return + } + spans = make([]*InternalSpan, numSpans) + for i := range spans { + spans[i] = &InternalSpan{Strings: strings} + o, err = spans[i].UnmarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, fmt.Sprintf("Failed to read span %d", i)) + return + } + } + return +} + +// UnmarshalMsg unmarshals the wire representation of a Span from a byte stream, updating the strings slice with new strings +// directly into an InternalSpan. Note that the Strings field of the InternalSpan must already be initialized. +func (span *InternalSpan) UnmarshalMsg(bts []byte) (o []byte, err error) { + if span.span == nil { + span.span = &Span{} + } + var numSpanFields uint32 + numSpanFields, o, err = limitedReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span fields header") + return + } + for numSpanFields > 0 { + numSpanFields-- + var fieldNum uint32 + fieldNum, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read a span field") + return + } + switch fieldNum { + case 1: + var service uint32 + service, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span service") + return + } + span.span.ServiceRef = service + case 2: + var name uint32 + name, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span name") + return + } + span.span.NameRef = name + case 3: + var resc uint32 + resc, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span resource") + return + } + span.span.ResourceRef = resc + case 4: + var spanID uint64 + spanID, o, err = msgp.ReadUint64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span spanID") + return + } + span.span.SpanID = spanID + case 5: + var parentID uint64 + parentID, o, err = msgp.ReadUint64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span parentID") + return + } + span.span.ParentID = parentID + case 6: + var start uint64 + start, o, err = msgp.ReadUint64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span start") + return + } + span.span.Start = start + case 7: + var duration uint64 + duration, o, err = msgp.ReadUint64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span duration") + return + } + span.span.Duration = duration + case 8: + var spanError bool + spanError, o, err = msgp.ReadBoolBytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span error") + return + } + span.span.Error = spanError + case 9: + var kvl map[uint32]*AnyValue + kvl, o, err = UnmarshalKeyValueMap(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span attributes") + return + } + span.span.Attributes = kvl + case 10: + var typ uint32 + typ, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span type") + return + } + span.span.TypeRef = typ + case 11: + var spanLinks []*SpanLink + spanLinks, o, err = UnmarshalSpanLinks(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span links") + return + } + span.span.Links = spanLinks + case 12: + var spanEvents []*SpanEvent + spanEvents, o, err = UnmarshalSpanEventList(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span events") + return + } + span.span.Events = spanEvents + case 13: + var env uint32 + env, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span env") + return + } + span.span.EnvRef = env + case 14: + var version uint32 + version, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span version") + return + } + span.span.VersionRef = version + case 15: + var component uint32 + component, o, err = UnmarshalStreamingString(o, span.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span component") + return + } + span.span.ComponentRef = component + case 16: + var kind uint32 + kind, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span kind") + return + } + span.span.Kind = SpanKind(kind) + default: + } + } + return +} + +// UnmarshalSpanEventList unmarshals a list of SpanEvents from a byte stream, updating the strings slice with new strings +func UnmarshalSpanEventList(bts []byte, strings *StringTable) (spanEvents []*SpanEvent, o []byte, err error) { + var numSpanEvents uint32 + numSpanEvents, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span event list header") + return + } + spanEvents = make([]*SpanEvent, numSpanEvents) + for i := range spanEvents { + spanEvents[i] = &SpanEvent{} + o, err = spanEvents[i].UnmarshalMsg(o, strings) + if err != nil { + err = msgp.WrapError(err, fmt.Sprintf("Failed to read span event %d", i)) + return + } + } + return +} + +// UnmarshalMsg unmarshals a SpanEvent from a byte stream, updating the strings slice with new strings +func (spanEvent *SpanEvent) UnmarshalMsg(bts []byte, strings *StringTable) (o []byte, err error) { + var numSpanEventFields uint32 + numSpanEventFields, o, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span event fields header") + return + } + for numSpanEventFields > 0 { + numSpanEventFields-- + var fieldNum uint32 + fieldNum, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read a span event field") + return + } + switch fieldNum { + case 1: + var time uint64 + time, o, err = msgp.ReadUint64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span event time") + return + } + spanEvent.Time = time + case 2: + var name uint32 + name, o, err = UnmarshalStreamingString(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span event name") + return + } + spanEvent.NameRef = name + case 3: + var kvl map[uint32]*AnyValue + kvl, o, err = UnmarshalKeyValueMap(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read span event attributes") + return + } + spanEvent.Attributes = kvl + default: + } + } + return +} + +// UnmarshalKeyValueMap unmarshals a map of key-value pairs from the byte stream, updating the StringTable with new strings +func UnmarshalKeyValueMap(bts []byte, strings *StringTable) (kvl map[uint32]*AnyValue, o []byte, err error) { + var numAttributes uint32 + numAttributes, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span attributes header") + return + } + if numAttributes > 0 && numAttributes%3 != 0 { + err = msgp.WrapError(err, fmt.Sprintf("Invalid number of span attributes %d - must be a multiple of 3", numAttributes)) + return + } + kvl = make(map[uint32]*AnyValue, numAttributes/3) + var i uint32 + for i < numAttributes { + var key uint32 + key, o, err = UnmarshalStreamingString(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read attribute key") + return + } + var value *AnyValue + value, o, err = UnmarshalAnyValue(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read attribute value") + return + } + kvl[key] = value + i += 3 + } + return +} + +// UnmarshalKeyValueList unmarshals a list of key-value pairs from the byte stream, updating the StringTable with new strings +func UnmarshalKeyValueList(bts []byte, strings *StringTable) (kvl []*KeyValue, o []byte, err error) { + var numAttributes uint32 + numAttributes, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span attributes header") + return + } + if numAttributes > 0 && numAttributes%3 != 0 { + err = msgp.WrapError(err, fmt.Sprintf("Invalid number of span attributes %d - must be a multiple of 3", numAttributes)) + return + } + kvl = make([]*KeyValue, numAttributes/3) + var i uint32 + for i < numAttributes { + var key uint32 + key, o, err = UnmarshalStreamingString(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read attribute key") + return + } + var value *AnyValue + value, o, err = UnmarshalAnyValue(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read attribute value") + return + } + kvl[i/3] = &KeyValue{Key: key, Value: value} + i += 3 + } + return +} + +// UnmarshalAnyValue unmarshals an AnyValue from a byte stream, updating the strings slice with new strings +func UnmarshalAnyValue(bts []byte, strings *StringTable) (value *AnyValue, o []byte, err error) { + value = &AnyValue{} + var valueType uint32 + valueType, o, err = msgp.ReadUint32Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read attribute value type") + return + } + switch valueType { + case 1: + var strValue uint32 + strValue, o, err = UnmarshalStreamingString(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read string attribute value") + return + } + value.Value = &AnyValue_StringValueRef{StringValueRef: strValue} + case 2: // boolValue + var boolValue bool + boolValue, o, err = msgp.ReadBoolBytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read bool attribute value") + return + } + value.Value = &AnyValue_BoolValue{BoolValue: boolValue} + case 3: // doubleValue + var doubleValue float64 + doubleValue, o, err = msgp.ReadFloat64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read double attribute value") + return + } + value.Value = &AnyValue_DoubleValue{DoubleValue: doubleValue} + case 4: // intValue + var intValue int64 + intValue, o, err = msgp.ReadInt64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read int attribute value") + return + } + value.Value = &AnyValue_IntValue{IntValue: intValue} + case 5: // bytesValue + var bytesValue []byte + bytesValue, o, err = msgp.ReadBytesBytes(o, nil) + if err != nil { + err = msgp.WrapError(err, "Failed to read bytes attribute value") + return + } + value.Value = &AnyValue_BytesValue{BytesValue: bytesValue} + case 6: // arrayValue + var numElements uint32 + numElements, o, err = limitedReadArrayHeaderBytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read array header") + return + } + if numElements%2 != 0 { + err = msgp.WrapError(err, "Invalid number of array elements, should be 2 elements per AnyValue") + return + } + arrayValue := make([]*AnyValue, numElements/2) + var i uint32 + for i < numElements { + var elemValue *AnyValue + elemValue, o, err = UnmarshalAnyValue(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read array element") + return + } + arrayValue[i/2] = elemValue + i += 2 + } + value.Value = &AnyValue_ArrayValue{ArrayValue: &ArrayValue{Values: arrayValue}} + case 7: // keyValueList + var kvl []*KeyValue + kvl, o, err = UnmarshalKeyValueList(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read keyValueList") + return + } + value.Value = &AnyValue_KeyValueList{KeyValueList: &KeyValueList{KeyValues: kvl}} + default: + err = msgp.WrapError(err, fmt.Sprintf("Unknown anyvalue type %d", valueType)) + return + } + return +} + +// UnmarshalStreamingString unmarshals a streaming string from a byte stream, updating the strings slice with new strings +// For streaming string details see pkg/trace/api/version.go for details +func UnmarshalStreamingString(bts []byte, strings *StringTable) (index uint32, o []byte, err error) { + if len(bts) < 1 { + err = msgp.WrapError(err, "Expected streaming string but EOF") + return + } + if isString(bts) { + var s string + s, o, err = msgp.ReadStringBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read streaming string as a string") + return + } + index = strings.Add(s) + } else { + index, o, err = msgp.ReadUint32Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read streaming string, failed to read uint32") + return + } + if int(index) >= strings.Len() { + err = msgp.WrapError(err, "Streaming string referenced an unseen string index") + return + } + } + return +} + +// Helper functions for msgp deserialization +const ( + first3 = 0xe0 + mtrue uint8 = 0xc3 + mfixstr uint8 = 0xa0 + mstr8 uint8 = 0xd9 + mstr16 uint8 = 0xda + mstr32 uint8 = 0xdb +) + +func isString(bts []byte) bool { + if isfixstr(bts[0]) { + return true + } + switch bts[0] { + case mstr8, mstr16, mstr32: + return true + default: + return false + } +} + +func isfixstr(b byte) bool { + return b&first3 == mfixstr +} + +// UnmarshalSpanLinks unmarshals a list of SpanLinks from a byte stream, updating the strings slice with new strings +func UnmarshalSpanLinks(bts []byte, strings *StringTable) (links []*SpanLink, o []byte, err error) { + var numLinks uint32 + numLinks, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span links header") + return + } + links = make([]*SpanLink, numLinks) + for i := range links { + links[i] = &SpanLink{} + o, err = links[i].UnmarshalMsg(o, strings) + if err != nil { + err = msgp.WrapError(err, fmt.Sprintf("Failed to read span link %d", i)) + return + } + } + return +} + +// UnmarshalMsg unmarshals a SpanLink from a byte stream, updating the strings slice with new strings +func (sl *SpanLink) UnmarshalMsg(bts []byte, strings *StringTable) (o []byte, err error) { + var numFields uint32 + numFields, o, err = limitedReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read span link fields header") + return + } + for numFields > 0 { + numFields-- + var fieldNum uint32 + fieldNum, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span link field") + return + } + switch fieldNum { + case 1: // traceID + sl.TraceID, o, err = msgp.ReadBytesBytes(o, nil) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace ID") + return + } + case 2: // spanID + sl.SpanID, o, err = msgp.ReadUint64Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read span ID") + return + } + case 3: // attributes + sl.Attributes, o, err = UnmarshalKeyValueMap(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read attributes") + return + } + case 4: // tracestate + sl.TracestateRef, o, err = UnmarshalStreamingString(o, strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracestate") + return + } + case 5: // flags + sl.Flags, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read flags") + return + } + default: + } + } + return +} + +// MarshalAttributesMap marshals a map of attributes into a byte stream +func MarshalAttributesMap(bts []byte, attributes map[uint32]*AnyValue, strings *StringTable, serStrings *SerializedStrings) (o []byte, err error) { + o = msgp.AppendArrayHeader(bts, uint32(len(attributes)*3)) // 3 entries per key value (key, type of value, value) + for k, v := range attributes { + o = serStrings.AppendStreamingString(strings.Get(k), k, o) + o, err = v.MarshalMsg(o, strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal attribute value") + return + } + } + return +} + +// Msgsize returns the size of the message when serialized. +func (val *AnyValue) Msgsize() int { + size := msgp.Uint32Size // For the type + switch v := val.Value.(type) { + case *AnyValue_StringValueRef: + size += msgp.Uint32Size + case *AnyValue_BoolValue: + size += msgp.BoolSize + case *AnyValue_DoubleValue: + size += msgp.Float64Size + case *AnyValue_IntValue: + size += msgp.Int64Size + case *AnyValue_BytesValue: + size += msgp.BytesPrefixSize + len(v.BytesValue) + case *AnyValue_ArrayValue: + size += msgp.ArrayHeaderSize + for _, value := range v.ArrayValue.Values { + size += value.Msgsize() + } + case *AnyValue_KeyValueList: + for _, value := range v.KeyValueList.KeyValues { + size += msgp.ArrayHeaderSize // Each KV is an array of 3 elements: (key, type of value, value) + size += msgp.Uint32Size + value.Value.Msgsize() // Key size + Value size (includes type) + } + } + return size +} + +// MarshalMsg marshals an AnyValue into a byte stream +func (val *AnyValue) MarshalMsg(bts []byte, strings *StringTable, serStrings *SerializedStrings) ([]byte, error) { + var err error + switch v := val.Value.(type) { + case *AnyValue_StringValueRef: + bts = msgp.AppendUint32(bts, 1) // write the type + bts = serStrings.AppendStreamingString(strings.Get(v.StringValueRef), v.StringValueRef, bts) + case *AnyValue_BoolValue: + bts = msgp.AppendUint32(bts, 2) // write the type + bts = msgp.AppendBool(bts, v.BoolValue) + case *AnyValue_DoubleValue: + bts = msgp.AppendUint32(bts, 3) // write the type + bts = msgp.AppendFloat64(bts, v.DoubleValue) + case *AnyValue_IntValue: + bts = msgp.AppendUint32(bts, 4) // write the type + bts = msgp.AppendInt64(bts, v.IntValue) + case *AnyValue_BytesValue: + bts = msgp.AppendUint32(bts, 5) // write the type + bts = msgp.AppendBytes(bts, v.BytesValue) + case *AnyValue_ArrayValue: + bts = msgp.AppendUint32(bts, 6) // write the type + bts = msgp.AppendArrayHeader(bts, uint32(len(v.ArrayValue.Values)*2)) + for _, value := range v.ArrayValue.Values { + bts, err = value.MarshalMsg(bts, strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal array element") + return bts, err + } + } + case *AnyValue_KeyValueList: + bts = msgp.AppendUint32(bts, 7) // write the type + bts = msgp.AppendArrayHeader(bts, uint32(len(v.KeyValueList.KeyValues)*3)) // 3 entries per key value (key, type of value, value) + for _, value := range v.KeyValueList.KeyValues { + bts, err = value.MarshalMsg(bts, strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal key value list element") + return bts, err + } + } + } + return bts, nil +} + +// MarshalMsg marshals a KeyValue into a byte stream +func (kv *KeyValue) MarshalMsg(bts []byte, strings *StringTable, serStrings *SerializedStrings) (o []byte, err error) { + o = serStrings.AppendStreamingString(strings.Get(kv.Key), kv.Key, bts) + o, err = kv.Value.MarshalMsg(o, strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal key value") + return + } + return +} + +// MarshalMsg marshals a SpanLink into a byte stream +func (sl *SpanLink) MarshalMsg(bts []byte, strings *StringTable, serStrings *SerializedStrings) (o []byte, err error) { + o = msgp.AppendMapHeader(bts, 5) + o = msgp.AppendUint32(o, 1) // traceID + o = msgp.AppendBytes(o, sl.TraceID) + o = msgp.AppendUint32(o, 2) // spanID + o = msgp.AppendUint64(o, sl.SpanID) + o = msgp.AppendUint32(o, 3) // attributes + o, err = MarshalAttributesMap(o, sl.Attributes, strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal attributes") + return + } + o = msgp.AppendUint32(o, 4) // tracestate + o = serStrings.AppendStreamingString(strings.Get(sl.TracestateRef), sl.TracestateRef, o) + o = msgp.AppendUint32(o, 5) // flags + o = msgp.AppendUint32(o, sl.Flags) + return +} + +// MarshalMsg marshals a SpanEvent into a byte stream +func (spanEvent *SpanEvent) MarshalMsg(bts []byte, strings *StringTable, serStrings *SerializedStrings) (o []byte, err error) { + o = msgp.AppendMapHeader(bts, 3) + o = msgp.AppendUint32(o, 1) // time + o = msgp.AppendUint64(o, spanEvent.Time) + o = msgp.AppendUint32(o, 2) // name + o = serStrings.AppendStreamingString(strings.Get(spanEvent.NameRef), spanEvent.NameRef, o) + o = msgp.AppendUint32(o, 3) // attributes + o, err = MarshalAttributesMap(o, spanEvent.Attributes, strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal attributes") + return + } + return +} + +// MarshalMsg marshals a Span into a byte stream +func (span *InternalSpan) MarshalMsg(bts []byte, serStrings *SerializedStrings) (o []byte, err error) { + // Count non-default fields to determine map header size + numFields := 0 + if span.span.ServiceRef != 0 { + numFields++ + } + if span.span.NameRef != 0 { + numFields++ + } + if span.span.ResourceRef != 0 { + numFields++ + } + if span.span.SpanID != 0 { + numFields++ + } + if span.span.ParentID != 0 { + numFields++ + } + if span.span.Start != 0 { + numFields++ + } + if span.span.Duration != 0 { + numFields++ + } + if span.span.Error { + numFields++ + } + if len(span.span.Attributes) > 0 { + numFields++ + } + if span.span.TypeRef != 0 { + numFields++ + } + if len(span.span.Links) > 0 { + numFields++ + } + if len(span.span.Events) > 0 { + numFields++ + } + if span.span.EnvRef != 0 { + numFields++ + } + if span.span.VersionRef != 0 { + numFields++ + } + if span.span.ComponentRef != 0 { + numFields++ + } + if span.span.Kind != 0 { + numFields++ + } + o = msgp.AppendMapHeader(bts, uint32(numFields)) + if span.span.ServiceRef != 0 { + o = msgp.AppendUint32(o, 1) // service + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.ServiceRef), span.span.ServiceRef, o) + } + if span.span.NameRef != 0 { + o = msgp.AppendUint32(o, 2) // name + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.NameRef), span.span.NameRef, o) + } + if span.span.ResourceRef != 0 { + o = msgp.AppendUint32(o, 3) // resource + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.ResourceRef), span.span.ResourceRef, o) + } + if span.span.SpanID != 0 { + o = msgp.AppendUint32(o, 4) // spanID + o = msgp.AppendUint64(o, span.span.SpanID) + } + if span.span.ParentID != 0 { + o = msgp.AppendUint32(o, 5) // parentID + o = msgp.AppendUint64(o, span.span.ParentID) + } + if span.span.Start != 0 { + o = msgp.AppendUint32(o, 6) // start + o = msgp.AppendUint64(o, span.span.Start) + } + if span.span.Duration != 0 { + o = msgp.AppendUint32(o, 7) // duration + o = msgp.AppendUint64(o, span.span.Duration) + } + if span.span.Error { + o = msgp.AppendUint32(o, 8) // error + o = msgp.AppendBool(o, span.span.Error) + } + if len(span.span.Attributes) > 0 { + o = msgp.AppendUint32(o, 9) // attributes + o, err = MarshalAttributesMap(o, span.span.Attributes, span.Strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal attributes") + return + } + } + if span.span.TypeRef != 0 { + o = msgp.AppendUint32(o, 10) // type + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.TypeRef), span.span.TypeRef, o) + } + if len(span.span.Links) > 0 { + o = msgp.AppendUint32(o, 11) // span links + o = msgp.AppendArrayHeader(o, uint32(len(span.span.Links))) + for _, link := range span.span.Links { + o, err = link.MarshalMsg(o, span.Strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal span link") + return + } + } + } + if len(span.span.Events) > 0 { + o = msgp.AppendUint32(o, 12) // span events + o = msgp.AppendArrayHeader(o, uint32(len(span.span.Events))) + for _, event := range span.span.Events { + o, err = event.MarshalMsg(o, span.Strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal span event") + return + } + } + } + if span.span.EnvRef != 0 { + o = msgp.AppendUint32(o, 13) // env + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.EnvRef), span.span.EnvRef, o) + } + if span.span.VersionRef != 0 { + o = msgp.AppendUint32(o, 14) // version + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.VersionRef), span.span.VersionRef, o) + } + if span.span.ComponentRef != 0 { + o = msgp.AppendUint32(o, 15) // component + o = serStrings.AppendStreamingString(span.Strings.Get(span.span.ComponentRef), span.span.ComponentRef, o) + } + if span.span.Kind != 0 { + o = msgp.AppendUint32(o, 16) // kind + o = msgp.AppendUint32(o, uint32(span.span.Kind)) + } + return +} + +// SerializedStrings is a helper type that tracks what strings have been serialized and where +// It is only good for one serialization +type SerializedStrings struct { + strIndexes []uint32 + curIndex uint32 +} + +// NewSerializedStrings creates a new SerializedStrings object used to track what strings have been serialized +// numStrings is the number of strings that will be serialized +func NewSerializedStrings(numStrings uint32) *SerializedStrings { + return &SerializedStrings{strIndexes: make([]uint32, numStrings), curIndex: 1} // index starts at 1 as "" is reserved at 0 +} + +// AppendStreamingString writes str to b if it hasn't been written before, otherwise it writes the serialization index +// strTableIndex is the location of str in the string table - this is used to track which strings have been written already +func (s *SerializedStrings) AppendStreamingString(str string, strTableIndex uint32, b []byte) []byte { + if s.strIndexes[strTableIndex] == 0 && str != "" { + // String is not yet serialized, serialize it + b = msgp.AppendString(b, str) + s.strIndexes[strTableIndex] = s.curIndex + s.curIndex++ + } else { + // TODO better names + // String is already serialized, write the index + index := s.strIndexes[strTableIndex] + b = msgp.AppendUint32(b, index) + } + return b +} diff --git a/ddtrace/tracer/idx/span.pb.go b/ddtrace/tracer/idx/span.pb.go new file mode 100644 index 0000000000..352b7674c9 --- /dev/null +++ b/ddtrace/tracer/idx/span.pb.go @@ -0,0 +1,904 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.8 +// protoc v5.29.3 +// source: datadog/trace/idx/span.proto + +package idx + +import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SpanKind is the type of span. Can be used to specify additional relationships between spans +// in addition to a parent/child relationship. +type SpanKind int32 + +const ( + // Unspecified. Do NOT use as default. + // Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. + SpanKind_SPAN_KIND_UNSPECIFIED SpanKind = 0 + // Indicates that the span represents an internal operation within an application, + // as opposed to an operations happening at the boundaries. Default value. + SpanKind_SPAN_KIND_INTERNAL SpanKind = 1 + // Indicates that the span covers server-side handling of an RPC or other + // remote network request. + SpanKind_SPAN_KIND_SERVER SpanKind = 2 + // Indicates that the span describes a request to some remote service. + SpanKind_SPAN_KIND_CLIENT SpanKind = 3 + // Indicates that the span describes a producer sending a message to a broker. + // Unlike CLIENT and SERVER, there is often no direct critical path latency relationship + // between producer and consumer spans. A PRODUCER span ends when the message was accepted + // by the broker while the logical processing of the message might span a much longer time. + SpanKind_SPAN_KIND_PRODUCER SpanKind = 4 + // Indicates that the span describes consumer receiving a message from a broker. + // Like the PRODUCER kind, there is often no direct critical path latency relationship + // between producer and consumer spans. + SpanKind_SPAN_KIND_CONSUMER SpanKind = 5 +) + +// Enum value maps for SpanKind. +var ( + SpanKind_name = map[int32]string{ + 0: "SPAN_KIND_UNSPECIFIED", + 1: "SPAN_KIND_INTERNAL", + 2: "SPAN_KIND_SERVER", + 3: "SPAN_KIND_CLIENT", + 4: "SPAN_KIND_PRODUCER", + 5: "SPAN_KIND_CONSUMER", + } + SpanKind_value = map[string]int32{ + "SPAN_KIND_UNSPECIFIED": 0, + "SPAN_KIND_INTERNAL": 1, + "SPAN_KIND_SERVER": 2, + "SPAN_KIND_CLIENT": 3, + "SPAN_KIND_PRODUCER": 4, + "SPAN_KIND_CONSUMER": 5, + } +) + +func (x SpanKind) Enum() *SpanKind { + p := new(SpanKind) + *p = x + return p +} + +func (x SpanKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SpanKind) Descriptor() protoreflect.EnumDescriptor { + return file_datadog_trace_idx_span_proto_enumTypes[0].Descriptor() +} + +func (SpanKind) Type() protoreflect.EnumType { + return &file_datadog_trace_idx_span_proto_enumTypes[0] +} + +func (x SpanKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SpanKind.Descriptor instead. +func (SpanKind) EnumDescriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{0} +} + +// AnyValue is a union of possible value types. +type AnyValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Value: + // + // *AnyValue_StringValueRef + // *AnyValue_BoolValue + // *AnyValue_DoubleValue + // *AnyValue_IntValue + // *AnyValue_BytesValue + // *AnyValue_ArrayValue + // *AnyValue_KeyValueList + Value isAnyValue_Value `protobuf_oneof:"value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AnyValue) Reset() { + *x = AnyValue{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AnyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AnyValue) ProtoMessage() {} + +func (x *AnyValue) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AnyValue.ProtoReflect.Descriptor instead. +func (*AnyValue) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{0} +} + +func (x *AnyValue) GetValue() isAnyValue_Value { + if x != nil { + return x.Value + } + return nil +} + +func (x *AnyValue) GetStringValueRef() uint32 { + if x != nil { + if x, ok := x.Value.(*AnyValue_StringValueRef); ok { + return x.StringValueRef + } + } + return 0 +} + +func (x *AnyValue) GetBoolValue() bool { + if x != nil { + if x, ok := x.Value.(*AnyValue_BoolValue); ok { + return x.BoolValue + } + } + return false +} + +func (x *AnyValue) GetDoubleValue() float64 { + if x != nil { + if x, ok := x.Value.(*AnyValue_DoubleValue); ok { + return x.DoubleValue + } + } + return 0 +} + +func (x *AnyValue) GetIntValue() int64 { + if x != nil { + if x, ok := x.Value.(*AnyValue_IntValue); ok { + return x.IntValue + } + } + return 0 +} + +func (x *AnyValue) GetBytesValue() []byte { + if x != nil { + if x, ok := x.Value.(*AnyValue_BytesValue); ok { + return x.BytesValue + } + } + return nil +} + +func (x *AnyValue) GetArrayValue() *ArrayValue { + if x != nil { + if x, ok := x.Value.(*AnyValue_ArrayValue); ok { + return x.ArrayValue + } + } + return nil +} + +func (x *AnyValue) GetKeyValueList() *KeyValueList { + if x != nil { + if x, ok := x.Value.(*AnyValue_KeyValueList); ok { + return x.KeyValueList + } + } + return nil +} + +type isAnyValue_Value interface { + isAnyValue_Value() +} + +type AnyValue_StringValueRef struct { + // stringValueRef specifies the string table ref of a string value. + StringValueRef uint32 `protobuf:"varint,1,opt,name=stringValueRef,proto3,oneof"` +} + +type AnyValue_BoolValue struct { + // boolValue specifies a bool value. + BoolValue bool `protobuf:"varint,2,opt,name=boolValue,proto3,oneof"` +} + +type AnyValue_DoubleValue struct { + // doubleValue specifies a double value. + DoubleValue float64 `protobuf:"fixed64,3,opt,name=doubleValue,proto3,oneof"` +} + +type AnyValue_IntValue struct { + // intValue specifies an int value. + IntValue int64 `protobuf:"varint,4,opt,name=intValue,proto3,oneof"` +} + +type AnyValue_BytesValue struct { + // bytesValue specifies a bytes value. + BytesValue []byte `protobuf:"bytes,5,opt,name=bytesValue,proto3,oneof"` +} + +type AnyValue_ArrayValue struct { + // arrayValue specifies an array value. + ArrayValue *ArrayValue `protobuf:"bytes,6,opt,name=arrayValue,proto3,oneof"` +} + +type AnyValue_KeyValueList struct { + // keyValueList specifies a list of key-value pairs. + KeyValueList *KeyValueList `protobuf:"bytes,7,opt,name=keyValueList,proto3,oneof"` +} + +func (*AnyValue_StringValueRef) isAnyValue_Value() {} + +func (*AnyValue_BoolValue) isAnyValue_Value() {} + +func (*AnyValue_DoubleValue) isAnyValue_Value() {} + +func (*AnyValue_IntValue) isAnyValue_Value() {} + +func (*AnyValue_BytesValue) isAnyValue_Value() {} + +func (*AnyValue_ArrayValue) isAnyValue_Value() {} + +func (*AnyValue_KeyValueList) isAnyValue_Value() {} + +// KeyValue is a key-value pair where key is a string table ref and value is an AnyValue. +type KeyValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + // key specifies the string table ref of a key. + Key uint32 `protobuf:"varint,1,opt,name=key,proto3" json:"key,omitempty"` + // value specifies a value. + Value *AnyValue `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *KeyValue) Reset() { + *x = KeyValue{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *KeyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValue) ProtoMessage() {} + +func (x *KeyValue) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead. +func (*KeyValue) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{1} +} + +func (x *KeyValue) GetKey() uint32 { + if x != nil { + return x.Key + } + return 0 +} + +func (x *KeyValue) GetValue() *AnyValue { + if x != nil { + return x.Value + } + return nil +} + +// ArrayValue is a repeated list of AnyValue that is needed since `oneof` in AnyValue +// cannot be `repeated` +type ArrayValue struct { + state protoimpl.MessageState `protogen:"open.v1"` + // values specifies a repeated list of AnyValue. + Values []*AnyValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ArrayValue) Reset() { + *x = ArrayValue{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ArrayValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArrayValue) ProtoMessage() {} + +func (x *ArrayValue) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArrayValue.ProtoReflect.Descriptor instead. +func (*ArrayValue) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{2} +} + +func (x *ArrayValue) GetValues() []*AnyValue { + if x != nil { + return x.Values + } + return nil +} + +// KeyValueList is a repeated list of KeyValue messages that is needed since `oneof` +// in AnyValue cannot be `repeated` or `map` +type KeyValueList struct { + state protoimpl.MessageState `protogen:"open.v1"` + // keyValues specifies a repeated list of KeyValue. + KeyValues []*KeyValue `protobuf:"bytes,1,rep,name=keyValues,proto3" json:"keyValues,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *KeyValueList) Reset() { + *x = KeyValueList{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *KeyValueList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeyValueList) ProtoMessage() {} + +func (x *KeyValueList) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KeyValueList.ProtoReflect.Descriptor instead. +func (*KeyValueList) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{3} +} + +func (x *KeyValueList) GetKeyValues() []*KeyValue { + if x != nil { + return x.KeyValues + } + return nil +} + +type SpanLink struct { + state protoimpl.MessageState `protogen:"open.v1"` + // traceID specifies the ID of the trace to which this span link belongs. + TraceID []byte `protobuf:"bytes,1,opt,name=traceID,proto3" json:"traceID,omitempty"` + // spanID specifies the ID of this span. + SpanID uint64 `protobuf:"fixed64,2,opt,name=spanID,proto3" json:"spanID,omitempty"` + // attributes specifies a map of attribute key string ref to any value. + Attributes map[uint32]*AnyValue `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // tracestateRef specifies the string table ref of the W3C tracestate. + TracestateRef uint32 `protobuf:"varint,4,opt,name=tracestateRef,proto3" json:"tracestateRef,omitempty"` + // flags specifies the W3C trace flags. Optional. If set, the high bit (bit 31) must be set. + Flags uint32 `protobuf:"varint,5,opt,name=flags,proto3" json:"flags,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SpanLink) Reset() { + *x = SpanLink{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SpanLink) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SpanLink) ProtoMessage() {} + +func (x *SpanLink) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SpanLink.ProtoReflect.Descriptor instead. +func (*SpanLink) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{4} +} + +func (x *SpanLink) GetTraceID() []byte { + if x != nil { + return x.TraceID + } + return nil +} + +func (x *SpanLink) GetSpanID() uint64 { + if x != nil { + return x.SpanID + } + return 0 +} + +func (x *SpanLink) GetAttributes() map[uint32]*AnyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *SpanLink) GetTracestateRef() uint32 { + if x != nil { + return x.TracestateRef + } + return 0 +} + +func (x *SpanLink) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + +type SpanEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + // time is the number of nanoseconds between the Unix epoch and this event. + Time uint64 `protobuf:"fixed64,1,opt,name=time,proto3" json:"time,omitempty"` + // nameRef specifies the string table ref of this event's name. + NameRef uint32 `protobuf:"varint,2,opt,name=nameRef,proto3" json:"nameRef,omitempty"` + // attributes is a mapping from attribute key string ref to any value. + Attributes map[uint32]*AnyValue `protobuf:"bytes,3,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SpanEvent) Reset() { + *x = SpanEvent{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SpanEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SpanEvent) ProtoMessage() {} + +func (x *SpanEvent) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SpanEvent.ProtoReflect.Descriptor instead. +func (*SpanEvent) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{5} +} + +func (x *SpanEvent) GetTime() uint64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *SpanEvent) GetNameRef() uint32 { + if x != nil { + return x.NameRef + } + return 0 +} + +func (x *SpanEvent) GetAttributes() map[uint32]*AnyValue { + if x != nil { + return x.Attributes + } + return nil +} + +type Span struct { + state protoimpl.MessageState `protogen:"open.v1"` + // serviceRef specifies the string table ref of this span's service name. + ServiceRef uint32 `protobuf:"varint,1,opt,name=serviceRef,proto3" json:"serviceRef,omitempty"` + // nameRef specifies the string table ref of this span's operation name. + NameRef uint32 `protobuf:"varint,2,opt,name=nameRef,proto3" json:"nameRef,omitempty"` + // resourceRef specifies the string table ref of this span's resource name. + ResourceRef uint32 `protobuf:"varint,3,opt,name=resourceRef,proto3" json:"resourceRef,omitempty"` + // spanID is the ID of this span. + SpanID uint64 `protobuf:"fixed64,4,opt,name=spanID,proto3" json:"spanID,omitempty"` + // parentID is the ID of this span's parent, or zero if this span has no parent. + ParentID uint64 `protobuf:"varint,5,opt,name=parentID,proto3" json:"parentID,omitempty"` + // start is the number of nanoseconds between the Unix epoch and the beginning of this span. + Start uint64 `protobuf:"fixed64,6,opt,name=start,proto3" json:"start,omitempty"` + // duration is the time length of this span in nanoseconds. + Duration uint64 `protobuf:"varint,7,opt,name=duration,proto3" json:"duration,omitempty"` + // error specifies if there is an error associated with this span. + Error bool `protobuf:"varint,8,opt,name=error,proto3" json:"error,omitempty"` + // attributes is a mapping from attribute key string ref to any value. + Attributes map[uint32]*AnyValue `protobuf:"bytes,9,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // typeRef is the string table ref of the type of the service with which this span is associated. Example values: web, db, lambda. + TypeRef uint32 `protobuf:"varint,10,opt,name=typeRef,proto3" json:"typeRef,omitempty"` + // span_links represents a collection of links, where each link defines a causal relationship between two spans. + Links []*SpanLink `protobuf:"bytes,11,rep,name=links,proto3" json:"links,omitempty"` + // spanEvents represent an event at an instant in time related to this span, but not necessarily during the span. + Events []*SpanEvent `protobuf:"bytes,12,rep,name=events,proto3" json:"events,omitempty"` + // envRef is the string table ref of the optional string environment of this span. + EnvRef uint32 `protobuf:"varint,13,opt,name=envRef,proto3" json:"envRef,omitempty"` + // versionRef is the string table ref of the optional string version of this span. + VersionRef uint32 `protobuf:"varint,14,opt,name=versionRef,proto3" json:"versionRef,omitempty"` + // componentRef is the string table ref of the string component name of this span. + ComponentRef uint32 `protobuf:"varint,15,opt,name=componentRef,proto3" json:"componentRef,omitempty"` + // kind is the SpanKind of this span as defined in the OTEL Specification. + Kind SpanKind `protobuf:"varint,16,opt,name=kind,proto3,enum=datadog.trace.idx.SpanKind" json:"kind,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Span) Reset() { + *x = Span{} + mi := &file_datadog_trace_idx_span_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Span) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Span) ProtoMessage() {} + +func (x *Span) ProtoReflect() protoreflect.Message { + mi := &file_datadog_trace_idx_span_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Span.ProtoReflect.Descriptor instead. +func (*Span) Descriptor() ([]byte, []int) { + return file_datadog_trace_idx_span_proto_rawDescGZIP(), []int{6} +} + +func (x *Span) GetServiceRef() uint32 { + if x != nil { + return x.ServiceRef + } + return 0 +} + +func (x *Span) GetNameRef() uint32 { + if x != nil { + return x.NameRef + } + return 0 +} + +func (x *Span) GetResourceRef() uint32 { + if x != nil { + return x.ResourceRef + } + return 0 +} + +func (x *Span) GetSpanID() uint64 { + if x != nil { + return x.SpanID + } + return 0 +} + +func (x *Span) GetParentID() uint64 { + if x != nil { + return x.ParentID + } + return 0 +} + +func (x *Span) GetStart() uint64 { + if x != nil { + return x.Start + } + return 0 +} + +func (x *Span) GetDuration() uint64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *Span) GetError() bool { + if x != nil { + return x.Error + } + return false +} + +func (x *Span) GetAttributes() map[uint32]*AnyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *Span) GetTypeRef() uint32 { + if x != nil { + return x.TypeRef + } + return 0 +} + +func (x *Span) GetLinks() []*SpanLink { + if x != nil { + return x.Links + } + return nil +} + +func (x *Span) GetEvents() []*SpanEvent { + if x != nil { + return x.Events + } + return nil +} + +func (x *Span) GetEnvRef() uint32 { + if x != nil { + return x.EnvRef + } + return 0 +} + +func (x *Span) GetVersionRef() uint32 { + if x != nil { + return x.VersionRef + } + return 0 +} + +func (x *Span) GetComponentRef() uint32 { + if x != nil { + return x.ComponentRef + } + return 0 +} + +func (x *Span) GetKind() SpanKind { + if x != nil { + return x.Kind + } + return SpanKind_SPAN_KIND_UNSPECIFIED +} + +var File_datadog_trace_idx_span_proto protoreflect.FileDescriptor + +const file_datadog_trace_idx_span_proto_rawDesc = "" + + "\n" + + "\x1cdatadog/trace/idx/span.proto\x12\x11datadog.trace.idx\"\xc9\x02\n" + + "\bAnyValue\x12(\n" + + "\x0estringValueRef\x18\x01 \x01(\rH\x00R\x0estringValueRef\x12\x1e\n" + + "\tboolValue\x18\x02 \x01(\bH\x00R\tboolValue\x12\"\n" + + "\vdoubleValue\x18\x03 \x01(\x01H\x00R\vdoubleValue\x12\x1c\n" + + "\bintValue\x18\x04 \x01(\x03H\x00R\bintValue\x12 \n" + + "\n" + + "bytesValue\x18\x05 \x01(\fH\x00R\n" + + "bytesValue\x12?\n" + + "\n" + + "arrayValue\x18\x06 \x01(\v2\x1d.datadog.trace.idx.ArrayValueH\x00R\n" + + "arrayValue\x12E\n" + + "\fkeyValueList\x18\a \x01(\v2\x1f.datadog.trace.idx.KeyValueListH\x00R\fkeyValueListB\a\n" + + "\x05value\"O\n" + + "\bKeyValue\x12\x10\n" + + "\x03key\x18\x01 \x01(\rR\x03key\x121\n" + + "\x05value\x18\x02 \x01(\v2\x1b.datadog.trace.idx.AnyValueR\x05value\"A\n" + + "\n" + + "ArrayValue\x123\n" + + "\x06values\x18\x01 \x03(\v2\x1b.datadog.trace.idx.AnyValueR\x06values\"I\n" + + "\fKeyValueList\x129\n" + + "\tkeyValues\x18\x01 \x03(\v2\x1b.datadog.trace.idx.KeyValueR\tkeyValues\"\xa1\x02\n" + + "\bSpanLink\x12\x18\n" + + "\atraceID\x18\x01 \x01(\fR\atraceID\x12\x16\n" + + "\x06spanID\x18\x02 \x01(\x06R\x06spanID\x12K\n" + + "\n" + + "attributes\x18\x03 \x03(\v2+.datadog.trace.idx.SpanLink.AttributesEntryR\n" + + "attributes\x12$\n" + + "\rtracestateRef\x18\x04 \x01(\rR\rtracestateRef\x12\x14\n" + + "\x05flags\x18\x05 \x01(\rR\x05flags\x1aZ\n" + + "\x0fAttributesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\rR\x03key\x121\n" + + "\x05value\x18\x02 \x01(\v2\x1b.datadog.trace.idx.AnyValueR\x05value:\x028\x01\"\xe3\x01\n" + + "\tSpanEvent\x12\x12\n" + + "\x04time\x18\x01 \x01(\x06R\x04time\x12\x18\n" + + "\anameRef\x18\x02 \x01(\rR\anameRef\x12L\n" + + "\n" + + "attributes\x18\x03 \x03(\v2,.datadog.trace.idx.SpanEvent.AttributesEntryR\n" + + "attributes\x1aZ\n" + + "\x0fAttributesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\rR\x03key\x121\n" + + "\x05value\x18\x02 \x01(\v2\x1b.datadog.trace.idx.AnyValueR\x05value:\x028\x01\"\x93\x05\n" + + "\x04Span\x12\x1e\n" + + "\n" + + "serviceRef\x18\x01 \x01(\rR\n" + + "serviceRef\x12\x18\n" + + "\anameRef\x18\x02 \x01(\rR\anameRef\x12 \n" + + "\vresourceRef\x18\x03 \x01(\rR\vresourceRef\x12\x16\n" + + "\x06spanID\x18\x04 \x01(\x06R\x06spanID\x12\x1a\n" + + "\bparentID\x18\x05 \x01(\x04R\bparentID\x12\x14\n" + + "\x05start\x18\x06 \x01(\x06R\x05start\x12\x1a\n" + + "\bduration\x18\a \x01(\x04R\bduration\x12\x14\n" + + "\x05error\x18\b \x01(\bR\x05error\x12G\n" + + "\n" + + "attributes\x18\t \x03(\v2'.datadog.trace.idx.Span.AttributesEntryR\n" + + "attributes\x12\x18\n" + + "\atypeRef\x18\n" + + " \x01(\rR\atypeRef\x121\n" + + "\x05links\x18\v \x03(\v2\x1b.datadog.trace.idx.SpanLinkR\x05links\x124\n" + + "\x06events\x18\f \x03(\v2\x1c.datadog.trace.idx.SpanEventR\x06events\x12\x16\n" + + "\x06envRef\x18\r \x01(\rR\x06envRef\x12\x1e\n" + + "\n" + + "versionRef\x18\x0e \x01(\rR\n" + + "versionRef\x12\"\n" + + "\fcomponentRef\x18\x0f \x01(\rR\fcomponentRef\x12/\n" + + "\x04kind\x18\x10 \x01(\x0e2\x1b.datadog.trace.idx.SpanKindR\x04kind\x1aZ\n" + + "\x0fAttributesEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\rR\x03key\x121\n" + + "\x05value\x18\x02 \x01(\v2\x1b.datadog.trace.idx.AnyValueR\x05value:\x028\x01*\x99\x01\n" + + "\bSpanKind\x12\x19\n" + + "\x15SPAN_KIND_UNSPECIFIED\x10\x00\x12\x16\n" + + "\x12SPAN_KIND_INTERNAL\x10\x01\x12\x14\n" + + "\x10SPAN_KIND_SERVER\x10\x02\x12\x14\n" + + "\x10SPAN_KIND_CLIENT\x10\x03\x12\x16\n" + + "\x12SPAN_KIND_PRODUCER\x10\x04\x12\x16\n" + + "\x12SPAN_KIND_CONSUMER\x10\x05B;Z9github.com/DataDog/datadog-agent/pkg/proto/pbgo/trace/idxb\x06proto3" + +var ( + file_datadog_trace_idx_span_proto_rawDescOnce sync.Once + file_datadog_trace_idx_span_proto_rawDescData []byte +) + +func file_datadog_trace_idx_span_proto_rawDescGZIP() []byte { + file_datadog_trace_idx_span_proto_rawDescOnce.Do(func() { + file_datadog_trace_idx_span_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_datadog_trace_idx_span_proto_rawDesc), len(file_datadog_trace_idx_span_proto_rawDesc))) + }) + return file_datadog_trace_idx_span_proto_rawDescData +} + +var file_datadog_trace_idx_span_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_datadog_trace_idx_span_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_datadog_trace_idx_span_proto_goTypes = []any{ + (SpanKind)(0), // 0: datadog.trace.idx.SpanKind + (*AnyValue)(nil), // 1: datadog.trace.idx.AnyValue + (*KeyValue)(nil), // 2: datadog.trace.idx.KeyValue + (*ArrayValue)(nil), // 3: datadog.trace.idx.ArrayValue + (*KeyValueList)(nil), // 4: datadog.trace.idx.KeyValueList + (*SpanLink)(nil), // 5: datadog.trace.idx.SpanLink + (*SpanEvent)(nil), // 6: datadog.trace.idx.SpanEvent + (*Span)(nil), // 7: datadog.trace.idx.Span + nil, // 8: datadog.trace.idx.SpanLink.AttributesEntry + nil, // 9: datadog.trace.idx.SpanEvent.AttributesEntry + nil, // 10: datadog.trace.idx.Span.AttributesEntry +} +var file_datadog_trace_idx_span_proto_depIdxs = []int32{ + 3, // 0: datadog.trace.idx.AnyValue.arrayValue:type_name -> datadog.trace.idx.ArrayValue + 4, // 1: datadog.trace.idx.AnyValue.keyValueList:type_name -> datadog.trace.idx.KeyValueList + 1, // 2: datadog.trace.idx.KeyValue.value:type_name -> datadog.trace.idx.AnyValue + 1, // 3: datadog.trace.idx.ArrayValue.values:type_name -> datadog.trace.idx.AnyValue + 2, // 4: datadog.trace.idx.KeyValueList.keyValues:type_name -> datadog.trace.idx.KeyValue + 8, // 5: datadog.trace.idx.SpanLink.attributes:type_name -> datadog.trace.idx.SpanLink.AttributesEntry + 9, // 6: datadog.trace.idx.SpanEvent.attributes:type_name -> datadog.trace.idx.SpanEvent.AttributesEntry + 10, // 7: datadog.trace.idx.Span.attributes:type_name -> datadog.trace.idx.Span.AttributesEntry + 5, // 8: datadog.trace.idx.Span.links:type_name -> datadog.trace.idx.SpanLink + 6, // 9: datadog.trace.idx.Span.events:type_name -> datadog.trace.idx.SpanEvent + 0, // 10: datadog.trace.idx.Span.kind:type_name -> datadog.trace.idx.SpanKind + 1, // 11: datadog.trace.idx.SpanLink.AttributesEntry.value:type_name -> datadog.trace.idx.AnyValue + 1, // 12: datadog.trace.idx.SpanEvent.AttributesEntry.value:type_name -> datadog.trace.idx.AnyValue + 1, // 13: datadog.trace.idx.Span.AttributesEntry.value:type_name -> datadog.trace.idx.AnyValue + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_datadog_trace_idx_span_proto_init() } +func file_datadog_trace_idx_span_proto_init() { + if File_datadog_trace_idx_span_proto != nil { + return + } + file_datadog_trace_idx_span_proto_msgTypes[0].OneofWrappers = []any{ + (*AnyValue_StringValueRef)(nil), + (*AnyValue_BoolValue)(nil), + (*AnyValue_DoubleValue)(nil), + (*AnyValue_IntValue)(nil), + (*AnyValue_BytesValue)(nil), + (*AnyValue_ArrayValue)(nil), + (*AnyValue_KeyValueList)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_datadog_trace_idx_span_proto_rawDesc), len(file_datadog_trace_idx_span_proto_rawDesc)), + NumEnums: 1, + NumMessages: 10, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_datadog_trace_idx_span_proto_goTypes, + DependencyIndexes: file_datadog_trace_idx_span_proto_depIdxs, + EnumInfos: file_datadog_trace_idx_span_proto_enumTypes, + MessageInfos: file_datadog_trace_idx_span_proto_msgTypes, + }.Build() + File_datadog_trace_idx_span_proto = out.File + file_datadog_trace_idx_span_proto_goTypes = nil + file_datadog_trace_idx_span_proto_depIdxs = nil +} diff --git a/ddtrace/tracer/idx/tracer_payload.go b/ddtrace/tracer/idx/tracer_payload.go new file mode 100644 index 0000000000..d7ab8e0071 --- /dev/null +++ b/ddtrace/tracer/idx/tracer_payload.go @@ -0,0 +1,405 @@ +// 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-present Datadog, Inc. + +package idx + +import ( + "fmt" + + "github.com/tinylib/msgp/msgp" +) + +const maxSize = 25 * 1e6 // maxSize protects the decoder from payloads lying about their size + +// UnmarshalMsg unmarshals a TracerPayload from a byte stream, updating the strings slice with new strings +// Returns any leftover bytes after the tracer payload is unmarshalled and any error that occurred +func (tp *InternalTracerPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { + if tp.Strings == nil { + tp.Strings = NewStringTable() + } + var numFields uint32 + numFields, o, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload fields header") + return + } + for numFields > 0 { + numFields-- + var fieldNum uint32 + fieldNum, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload field") + return + } + switch fieldNum { + case 1: + // If strings are sent they must be sent first. + // TODO: this should always be an error + if tp.Strings.Len() > 1 { + err = msgp.WrapError(err, "Unexpected strings attribute, strings must be sent first") + return + } + o, err = unmarshalStringTable(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload strings") + return + } + case 2: + tp.containerIDRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload containerID") + return + } + case 3: + tp.languageNameRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload languageName") + return + } + case 4: + tp.languageVersionRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload languageVersion") + return + } + case 5: + tp.tracerVersionRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload tracerVersion") + return + } + case 6: + tp.runtimeIDRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload runtimeID") + return + } + case 7: + tp.envRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload env") + return + } + case 8: + tp.hostnameRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload hostname") + return + } + case 9: + tp.appVersionRef, o, err = UnmarshalStreamingString(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload appVersion") + return + } + case 10: + tp.Attributes, o, err = UnmarshalKeyValueMap(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload attributes") + return + } + case 11: + tp.Chunks, o, err = UnmarshalTraceChunkList(o, tp.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read tracer payload chunks") + return + } + default: + } + } + return +} + +func limitedReadArrayHeaderBytes(bts []byte) (sz uint32, o []byte, err error) { + sz, o, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + return + } + if sz > maxSize { + err = msgp.WrapError(err, "Array too large") + return + } + return +} + +func limitedReadMapHeaderBytes(bts []byte) (sz uint32, o []byte, err error) { + sz, o, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + return + } + if sz > maxSize { + err = msgp.WrapError(err, "Map too large") + return + } + return +} + +// unmarshalStringTable unmarshals a list of strings from a byte stream +func unmarshalStringTable(bts []byte, strings *StringTable) (o []byte, err error) { + var numStrings uint32 + numStrings, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read string list header") + return + } + for numStrings > 0 { + numStrings-- + var str string + str, o, err = msgp.ReadStringBytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read string") + } + if str == "" { + continue // Skip empty strings, we already have an empty string at index 0 + } + strings.addUnchecked(str) //We don't need to check for duplicates because the string table should arrive with only unique strings + } + return +} + +// MarshalMsg marshals a TracerPayload into a byte stream +func (tp *InternalTracerPayload) MarshalMsg(bts []byte) (o []byte, err error) { + serStrings := NewSerializedStrings(uint32(tp.Strings.Len())) + // Count non-default fields to determine map header size + numFields := 0 + if tp.containerIDRef != 0 { + numFields++ + } + if tp.languageNameRef != 0 { + numFields++ + } + if tp.languageVersionRef != 0 { + numFields++ + } + if tp.tracerVersionRef != 0 { + numFields++ + } + if tp.runtimeIDRef != 0 { + numFields++ + } + if tp.envRef != 0 { + numFields++ + } + if tp.hostnameRef != 0 { + numFields++ + } + if tp.appVersionRef != 0 { + numFields++ + } + if len(tp.Attributes) > 0 { + numFields++ + } + if len(tp.Chunks) > 0 { + numFields++ + } + o = msgp.AppendMapHeader(bts, uint32(numFields)) + if tp.containerIDRef != 0 { + o = msgp.AppendUint32(o, 2) // containerID + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.containerIDRef), tp.containerIDRef, o) + } + if tp.languageNameRef != 0 { + o = msgp.AppendUint32(o, 3) // languageName + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.languageNameRef), tp.languageNameRef, o) + } + if tp.languageVersionRef != 0 { + o = msgp.AppendUint32(o, 4) // languageVersion + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.languageVersionRef), tp.languageVersionRef, o) + } + if tp.tracerVersionRef != 0 { + o = msgp.AppendUint32(o, 5) // tracerVersion + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.tracerVersionRef), tp.tracerVersionRef, o) + } + if tp.runtimeIDRef != 0 { + o = msgp.AppendUint32(o, 6) // runtimeID + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.runtimeIDRef), tp.runtimeIDRef, o) + } + if tp.envRef != 0 { + o = msgp.AppendUint32(o, 7) // env + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.envRef), tp.envRef, o) + } + if tp.hostnameRef != 0 { + o = msgp.AppendUint32(o, 8) // hostname + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.hostnameRef), tp.hostnameRef, o) + } + if tp.appVersionRef != 0 { + o = msgp.AppendUint32(o, 9) // appVersion + o = serStrings.AppendStreamingString(tp.Strings.Get(tp.appVersionRef), tp.appVersionRef, o) + } + if len(tp.Attributes) > 0 { + o = msgp.AppendUint32(o, 10) // attributes + o, err = MarshalAttributesMap(o, tp.Attributes, tp.Strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal attributes") + return + } + } + if len(tp.Chunks) > 0 { + o = msgp.AppendUint32(o, 11) // chunks + o = msgp.AppendArrayHeader(o, uint32(len(tp.Chunks))) + for _, chunk := range tp.Chunks { + o, err = chunk.MarshalMsg(o, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal trace chunk") + return + } + } + } + return +} + +// UnmarshalTraceChunkList unmarshals a list of TraceChunks from a byte stream, updating the strings slice with new strings +func UnmarshalTraceChunkList(bts []byte, strings *StringTable) (chunks []*InternalTraceChunk, o []byte, err error) { + var numChunks uint32 + numChunks, o, err = limitedReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk list header") + return + } + chunks = make([]*InternalTraceChunk, numChunks) + for i := range chunks { + chunks[i] = &InternalTraceChunk{Strings: strings} + o, err = chunks[i].UnmarshalMsg(o) + if err != nil { + err = msgp.WrapError(err, fmt.Sprintf("Failed to read trace chunk %d", i)) + return + } + } + return +} + +// UnmarshalMsg unmarshals a TraceChunk from a byte stream, updating the strings slice with new strings +func (tc *InternalTraceChunk) UnmarshalMsg(bts []byte) (o []byte, err error) { + var numFields uint32 + numFields, o, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk fields header") + return + } + for numFields > 0 { + numFields-- + var fieldNum uint32 + fieldNum, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk field") + return + } + switch fieldNum { + case 1: + tc.Priority, o, err = msgp.ReadInt32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk priority") + return + } + case 2: + tc.originRef, o, err = UnmarshalStreamingString(o, tc.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk origin") + return + } + case 3: + tc.Attributes, o, err = UnmarshalKeyValueMap(o, tc.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk attributes") + return + } + case 4: + tc.Spans, o, err = UnmarshalSpanList(o, tc.Strings) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk spans") + return + } + case 5: + tc.DroppedTrace, o, err = msgp.ReadBoolBytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk droppedTrace") + return + } + case 6: + tc.TraceID, o, err = msgp.ReadBytesBytes(o, nil) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk traceID") + return + } + case 7: + tc.samplingMechanism, o, err = msgp.ReadUint32Bytes(o) + if err != nil { + err = msgp.WrapError(err, "Failed to read trace chunk samplingMechanism") + return + } + default: + fmt.Printf("Unknown trace chunk field number %d\n", fieldNum) + return + } + } + return +} + +// MarshalMsg marshals a TraceChunk into a byte stream +func (tc *InternalTraceChunk) MarshalMsg(bts []byte, serStrings *SerializedStrings) (o []byte, err error) { + // Count non-default fields to determine map header size + numFields := 0 + if tc.Priority != 0 { + numFields++ + } + if tc.originRef != 0 { + numFields++ + } + if len(tc.Attributes) > 0 { + numFields++ + } + if len(tc.Spans) > 0 { + numFields++ + } + if tc.DroppedTrace { + numFields++ + } + if len(tc.TraceID) > 0 { + numFields++ + } + if tc.samplingMechanism != 0 { + numFields++ + } + o = msgp.AppendMapHeader(bts, uint32(numFields)) + if tc.Priority != 0 { + o = msgp.AppendUint32(o, 1) // priority + o = msgp.AppendInt32(o, tc.Priority) + } + if tc.originRef != 0 { + o = msgp.AppendUint32(o, 2) // origin + o = serStrings.AppendStreamingString(tc.Strings.Get(tc.originRef), tc.originRef, o) + } + if len(tc.Attributes) > 0 { + o = msgp.AppendUint32(o, 3) // attributes + o, err = MarshalAttributesMap(o, tc.Attributes, tc.Strings, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal attributes") + return + } + } + if len(tc.Spans) > 0 { + o = msgp.AppendUint32(o, 4) // spans + o = msgp.AppendArrayHeader(o, uint32(len(tc.Spans))) + for _, span := range tc.Spans { + o, err = span.MarshalMsg(o, serStrings) + if err != nil { + err = msgp.WrapError(err, "Failed to marshal span") + return + } + } + } + if tc.DroppedTrace { + o = msgp.AppendUint32(o, 5) // droppedTrace + o = msgp.AppendBool(o, tc.DroppedTrace) + } + if len(tc.TraceID) > 0 { + o = msgp.AppendUint32(o, 6) // traceID + o = msgp.AppendBytes(o, tc.TraceID) + } + if tc.samplingMechanism != 0 { + o = msgp.AppendUint32(o, 7) // samplingMechanism + o = msgp.AppendUint32(o, tc.samplingMechanism) + } + return +} diff --git a/ddtrace/tracer/log.go b/ddtrace/tracer/log.go index cb3d6797e5..a9747d4bdd 100644 --- a/ddtrace/tracer/log.go +++ b/ddtrace/tracer/log.go @@ -69,8 +69,12 @@ type startupInfo struct { // checkEndpoint tries to connect to the URL specified by endpoint. // If the endpoint is not reachable, checkEndpoint returns an error // explaining why. -func checkEndpoint(c *http.Client, endpoint string) error { - req, err := http.NewRequest("POST", endpoint, bytes.NewReader([]byte{0x90})) +func checkEndpoint(c *http.Client, endpoint string, v1Test bool) error { + emptyPayload := []byte{0x90} + if v1Test { + emptyPayload = []byte{0x80} + } + req, err := http.NewRequest("POST", endpoint, bytes.NewReader(emptyPayload)) if err != nil { return fmt.Errorf("cannot create http request: %s", err.Error()) } @@ -162,7 +166,7 @@ func logStartup(t *tracer) { info.SampleRateLimit = fmt.Sprintf("%v", limit) } if !t.config.logToStdout { - if err := checkEndpoint(t.config.httpClient, t.config.transport.endpoint()); err != nil { + if err := checkEndpoint(t.config.httpClient, t.config.transport.endpoint(), t.config.v1Test); err != nil { info.AgentError = fmt.Sprintf("%s", err.Error()) log.Warn("DIAGNOSTICS Unable to reach agent intake: %s", err.Error()) } diff --git a/ddtrace/tracer/option.go b/ddtrace/tracer/option.go index 9c03f91cda..6b625ea2ba 100644 --- a/ddtrace/tracer/option.go +++ b/ddtrace/tracer/option.go @@ -322,6 +322,9 @@ type config struct { // traceProtocol specifies the trace protocol to use. traceProtocol float64 + + // v1Test enables the v1 payload format when set to true. + v1Test bool } // orchestrionConfig contains Orchestrion configuration. @@ -479,6 +482,7 @@ func newConfig(opts ...StartOption) (*config, error) { if c.debugAbandonedSpans { c.spanTimeout = internal.DurationEnv("DD_TRACE_ABANDONED_SPAN_TIMEOUT", 10*time.Minute) } + c.v1Test = internal.BoolEnv("DD_V1_TEST", false) c.statsComputationEnabled = internal.BoolEnv("DD_TRACE_STATS_COMPUTATION_ENABLED", true) c.dataStreamsMonitoringEnabled, _, _ = stableconfig.Bool("DD_DATA_STREAMS_ENABLED", false) c.partialFlushEnabled = internal.BoolEnv("DD_TRACE_PARTIAL_FLUSH_ENABLED", false) @@ -567,7 +571,7 @@ func newConfig(opts ...StartOption) (*config, error) { } } if c.transport == nil { - c.transport = newHTTPTransport(c.agentURL.String(), c.httpClient) + c.transport = newHTTPTransport(c.agentURL.String(), c.httpClient, c.v1Test) } if c.propagator == nil { envKey := "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH" diff --git a/ddtrace/tracer/payload.go b/ddtrace/tracer/payload.go index 3cb58ef18a..591003943b 100644 --- a/ddtrace/tracer/payload.go +++ b/ddtrace/tracer/payload.go @@ -231,6 +231,17 @@ func newPayload(protocol float64) payload { } } +// newPayloadWithConfig returns a payload based on the configuration. +// If v1Test is enabled, it returns a v1Payload, otherwise it returns a safePayload. +func newPayloadWithConfig(protocol float64, v1Test bool) payload { + if v1Test { + return newV1Payload(protocol) + } + return &safePayload{ + p: newUnsafePayload(protocol), + } +} + // safePayload provides a thread-safe wrapper around unsafePayload. type safePayload struct { mu sync.RWMutex diff --git a/ddtrace/tracer/payload_v1.go b/ddtrace/tracer/payload_v1.go new file mode 100644 index 0000000000..400ec56c5f --- /dev/null +++ b/ddtrace/tracer/payload_v1.go @@ -0,0 +1,479 @@ +// 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-present Datadog, Inc. + +package tracer + +import ( + "bytes" + "encoding/binary" + "fmt" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer/idx" +) + +// v1Payload implements the payload interface for the v1 efficient trace payload format. +// It uses string interning and the InternalTracerPayload structure for efficient serialization. +type v1Payload struct { + // mu protects concurrent access to the payload + mu sync.RWMutex + + // internalPayload holds the v1 format data + internalPayload *idx.InternalTracerPayload + + // buf holds the serialized data + buf bytes.Buffer + + // reader is used for reading the contents of buf + reader *bytes.Reader + + // count specifies the number of items in the stream + count uint32 + + // protocolVersion specifies the trace protocol version to use + protocolVersion float64 +} + +// newV1Payload returns a ready to use v1 payload. +func newV1Payload(protocol float64) *v1Payload { + return &v1Payload{ + internalPayload: &idx.InternalTracerPayload{ + Strings: idx.NewStringTable(), + Attributes: make(map[uint32]*idx.AnyValue), + Chunks: make([]*idx.InternalTraceChunk, 0), + }, + protocolVersion: protocol, + } +} + +// push pushes a new item (spanList) into the v1 payload. +func (p *v1Payload) push(t spanList) (stats payloadStats, err error) { + p.mu.Lock() + defer p.mu.Unlock() + + // Convert spanList to InternalTracerPayload format + chunk, err := p.convertSpanListToChunk(t) + if err != nil { + return payloadStats{}, err + } + + // Add the chunk to the payload + p.internalPayload.Chunks = append(p.internalPayload.Chunks, chunk) + + // Update stats + atomic.AddUint32(&p.count, 1) + + // Serialize the updated payload + if err := p.serializePayload(); err != nil { + return payloadStats{}, err + } + + return p.statsUnsafe(), nil +} + +// convertSpanListToChunk converts a spanList to an InternalTraceChunk. +func (p *v1Payload) convertSpanListToChunk(spans spanList) (*idx.InternalTraceChunk, error) { + if len(spans) == 0 { + return &idx.InternalTraceChunk{ + Strings: p.internalPayload.Strings, + Priority: 0, + Attributes: make(map[uint32]*idx.AnyValue), + Spans: make([]*idx.InternalSpan, 0), + DroppedTrace: false, + TraceID: make([]byte, 16), + }, nil + } + + // Convert spans to InternalSpan format + internalSpans := make([]*idx.InternalSpan, len(spans)) + for i, span := range spans { + internalSpan, err := p.convertSpanToInternal(span) + if err != nil { + return nil, err + } + internalSpans[i] = internalSpan + } + + // Extract trace ID from the first span + var traceID []byte + if len(spans) > 0 { + traceID = p.extractTraceID(spans[0]) + } + + // Create the chunk + chunk := &idx.InternalTraceChunk{ + Strings: p.internalPayload.Strings, + Priority: 0, // Priority is not available in the Span struct + Attributes: make(map[uint32]*idx.AnyValue), + Spans: internalSpans, + DroppedTrace: false, + TraceID: traceID, + } + chunk.SetSamplingMechanism(0) + + // Extract chunk-level attributes from the first span + p.extractChunkAttributes(spans[0], chunk) + + return chunk, nil +} + +// convertSpanToInternal converts a Span to an InternalSpan. +func (p *v1Payload) convertSpanToInternal(span *Span) (*idx.InternalSpan, error) { + // Create the internal span structure + internalSpan := &idx.Span{ + ServiceRef: p.internalPayload.Strings.Add(span.service), + NameRef: p.internalPayload.Strings.Add(span.name), + ResourceRef: p.internalPayload.Strings.Add(span.resource), + SpanID: span.spanID, + ParentID: span.parentID, + Start: uint64(span.start), + Duration: uint64(span.duration), + Error: span.error > 0, + Attributes: make(map[uint32]*idx.AnyValue), + TypeRef: p.internalPayload.Strings.Add(span.spanType), + EnvRef: p.internalPayload.Strings.Add(span.meta["env"]), + VersionRef: p.internalPayload.Strings.Add(span.meta["version"]), + ComponentRef: p.internalPayload.Strings.Add(span.meta["component"]), + Kind: p.convertSpanKind(span.meta["kind"]), + Links: p.convertSpanLinks(span.spanLinks), + Events: p.convertSpanEvents(span.spanEvents), + } + + // Convert span attributes + for k, v := range span.meta { + if k == "env" || k == "version" || k == "component" || k == "kind" { + continue // Already handled above + } + internalSpan.Attributes[p.internalPayload.Strings.Add(k)] = &idx.AnyValue{ + Value: &idx.AnyValue_StringValueRef{ + StringValueRef: p.internalPayload.Strings.Add(v), + }, + } + } + + // Convert metrics + for k, v := range span.metrics { + internalSpan.Attributes[p.internalPayload.Strings.Add(k)] = &idx.AnyValue{ + Value: &idx.AnyValue_DoubleValue{ + DoubleValue: v, + }, + } + } + + // Convert meta struct + for k, v := range span.metaStruct { + // Convert the value to bytes + var bytesValue []byte + if b, ok := v.([]byte); ok { + bytesValue = b + } else { + // Convert to string and then to bytes as fallback + bytesValue = []byte(fmt.Sprintf("%v", v)) + } + internalSpan.Attributes[p.internalPayload.Strings.Add(k)] = &idx.AnyValue{ + Value: &idx.AnyValue_BytesValue{ + BytesValue: bytesValue, + }, + } + } + + return idx.NewInternalSpan(p.internalPayload.Strings, internalSpan), nil +} + +// convertSpanKind converts a string span kind to the idx.SpanKind enum. +func (p *v1Payload) convertSpanKind(kindStr string) idx.SpanKind { + switch kindStr { + case "server": + return idx.SpanKind_SPAN_KIND_SERVER + case "client": + return idx.SpanKind_SPAN_KIND_CLIENT + case "producer": + return idx.SpanKind_SPAN_KIND_PRODUCER + case "consumer": + return idx.SpanKind_SPAN_KIND_CONSUMER + case "internal": + return idx.SpanKind_SPAN_KIND_INTERNAL + default: + return idx.SpanKind_SPAN_KIND_INTERNAL + } +} + +// convertSpanLinks converts span links to the internal format. +func (p *v1Payload) convertSpanLinks(links []SpanLink) []*idx.SpanLink { + if len(links) == 0 { + return nil + } + + internalLinks := make([]*idx.SpanLink, len(links)) + for i, link := range links { + linkTraceID := make([]byte, 16) + binary.BigEndian.PutUint64(linkTraceID[8:], link.TraceID) + binary.BigEndian.PutUint64(linkTraceID[:8], link.TraceIDHigh) + + internalLinks[i] = &idx.SpanLink{ + TraceID: linkTraceID, + SpanID: link.SpanID, + TracestateRef: p.internalPayload.Strings.Add(link.Tracestate), + Flags: link.Flags, + Attributes: p.convertAttributesMap(link.Attributes), + } + } + + return internalLinks +} + +// convertSpanEvents converts span events to the internal format. +func (p *v1Payload) convertSpanEvents(events []spanEvent) []*idx.SpanEvent { + if len(events) == 0 { + return nil + } + + internalEvents := make([]*idx.SpanEvent, len(events)) + for i, event := range events { + internalEvents[i] = &idx.SpanEvent{ + Time: uint64(event.TimeUnixNano), + NameRef: p.internalPayload.Strings.Add(event.Name), + Attributes: p.convertSpanEventAttributes(event.RawAttributes), + } + } + + return internalEvents +} + +// convertSpanEventAttributes converts span event attributes to the internal format. +func (p *v1Payload) convertSpanEventAttributes(attrs map[string]interface{}) map[uint32]*idx.AnyValue { + if len(attrs) == 0 { + return nil + } + + internalAttrs := make(map[uint32]*idx.AnyValue, len(attrs)) + for k, v := range attrs { + keyRef := p.internalPayload.Strings.Add(k) + internalAttrs[keyRef] = p.convertAnyValue(v) + } + + return internalAttrs +} + +// convertAnyValue converts an interface{} to an idx.AnyValue. +func (p *v1Payload) convertAnyValue(v interface{}) *idx.AnyValue { + switch val := v.(type) { + case string: + return &idx.AnyValue{ + Value: &idx.AnyValue_StringValueRef{ + StringValueRef: p.internalPayload.Strings.Add(val), + }, + } + case bool: + return &idx.AnyValue{ + Value: &idx.AnyValue_BoolValue{ + BoolValue: val, + }, + } + case int64: + return &idx.AnyValue{ + Value: &idx.AnyValue_IntValue{ + IntValue: val, + }, + } + case float64: + return &idx.AnyValue{ + Value: &idx.AnyValue_DoubleValue{ + DoubleValue: val, + }, + } + case []interface{}: + values := make([]*idx.AnyValue, len(val)) + for i, item := range val { + values[i] = p.convertAnyValue(item) + } + return &idx.AnyValue{ + Value: &idx.AnyValue_ArrayValue{ + ArrayValue: &idx.ArrayValue{ + Values: values, + }, + }, + } + default: + // Convert to string as fallback + return &idx.AnyValue{ + Value: &idx.AnyValue_StringValueRef{ + StringValueRef: p.internalPayload.Strings.Add(strings.TrimSpace(fmt.Sprintf("%v", val))), + }, + } + } +} + +// convertAttributesMap converts a map[string]string to the internal format. +func (p *v1Payload) convertAttributesMap(attrs map[string]string) map[uint32]*idx.AnyValue { + if len(attrs) == 0 { + return nil + } + + internalAttrs := make(map[uint32]*idx.AnyValue, len(attrs)) + for k, v := range attrs { + internalAttrs[p.internalPayload.Strings.Add(k)] = &idx.AnyValue{ + Value: &idx.AnyValue_StringValueRef{ + StringValueRef: p.internalPayload.Strings.Add(v), + }, + } + } + + return internalAttrs +} + +// extractTraceID extracts the 128-bit trace ID from a span. +func (p *v1Payload) extractTraceID(span *Span) []byte { + traceID := make([]byte, 16) + binary.BigEndian.PutUint64(traceID[8:], span.traceID) + // TraceIDHigh is not available in the Span struct, so we use 0 + binary.BigEndian.PutUint64(traceID[:8], 0) + return traceID +} + +// extractChunkAttributes extracts chunk-level attributes from a span. +func (p *v1Payload) extractChunkAttributes(span *Span, chunk *idx.InternalTraceChunk) { + // Extract sampling mechanism from _dd.p.dm tag + if dmStr, exists := span.meta["_dd.p.dm"]; exists { + valueStr := dmStr + if strings.HasPrefix(dmStr, "-") { + valueStr = strings.TrimPrefix(dmStr, "-") + } + if val, err := strconv.ParseUint(valueStr, 10, 32); err == nil { + chunk.SetSamplingMechanism(uint32(val)) + } + } + + if priority, exists := span.metrics["_sampling_priority_v1"]; exists { + chunk.Priority = int32(priority) + } + + // Add other chunk-level attributes (excluding _dd.p.dm) + for k, v := range span.meta { + if k == "_dd.p.dm" { + continue + } + chunk.Attributes[p.internalPayload.Strings.Add(k)] = &idx.AnyValue{ + Value: &idx.AnyValue_StringValueRef{ + StringValueRef: p.internalPayload.Strings.Add(v), + }, + } + } +} + +// serializePayload serializes the internal payload to the buffer. +func (p *v1Payload) serializePayload() error { + p.buf.Reset() + + // Marshal the internal payload + data, err := p.internalPayload.MarshalMsg(nil) + if err != nil { + return err + } + + // Write the data to buffer + _, err = p.buf.Write(data) + return err +} + +// itemCount returns the number of items available in the stream. +func (p *v1Payload) itemCount() int { + return int(atomic.LoadUint32(&p.count)) +} + +// size returns the payload size in bytes. +func (p *v1Payload) size() int { + p.mu.RLock() + defer p.mu.RUnlock() + return p.buf.Len() +} + +// reset sets up the payload to be read a second time. +func (p *v1Payload) reset() { + p.mu.Lock() + defer p.mu.Unlock() + + if p.reader != nil { + p.reader.Seek(0, 0) + } +} + +// clear empties the payload buffers. +func (p *v1Payload) clear() { + p.mu.Lock() + defer p.mu.Unlock() + + p.buf = bytes.Buffer{} + p.reader = nil + p.internalPayload = &idx.InternalTracerPayload{ + Strings: idx.NewStringTable(), + Attributes: make(map[uint32]*idx.AnyValue), + Chunks: make([]*idx.InternalTraceChunk, 0), + } + atomic.StoreUint32(&p.count, 0) +} + +// Read implements io.Reader. It reads from the serialized payload. +func (p *v1Payload) Read(b []byte) (n int, err error) { + p.mu.Lock() + defer p.mu.Unlock() + + if p.reader == nil { + p.reader = bytes.NewReader(p.buf.Bytes()) + } + return p.reader.Read(b) +} + +// Write implements io.Writer. It writes data directly to the buffer. +func (p *v1Payload) Write(data []byte) (n int, err error) { + p.mu.Lock() + defer p.mu.Unlock() + return p.buf.Write(data) +} + +// Close implements io.Closer. +func (p *v1Payload) Close() error { + return nil +} + +// grow grows the buffer to ensure it can accommodate n more bytes. +func (p *v1Payload) grow(n int) { + p.mu.Lock() + defer p.mu.Unlock() + p.buf.Grow(n) +} + +// recordItem records that an item was added and updates the header. +func (p *v1Payload) recordItem() { + atomic.AddUint32(&p.count, 1) +} + +// stats returns the current stats of the payload. +func (p *v1Payload) stats() payloadStats { + return payloadStats{ + size: p.size(), + itemCount: int(atomic.LoadUint32(&p.count)), + } +} + +// statsUnsafe returns the current stats of the payload without acquiring locks. +// This should only be called when the caller already holds the appropriate lock. +func (p *v1Payload) statsUnsafe() payloadStats { + return payloadStats{ + size: p.buf.Len(), + itemCount: int(atomic.LoadUint32(&p.count)), + } +} + +// protocol returns the protocol version of the payload. +func (p *v1Payload) protocol() float64 { + return p.protocolVersion +} + +// Ensure v1Payload implements the payload interface +var _ payload = (*v1Payload)(nil) diff --git a/ddtrace/tracer/payload_v1_test.go b/ddtrace/tracer/payload_v1_test.go new file mode 100644 index 0000000000..06f5febe5b --- /dev/null +++ b/ddtrace/tracer/payload_v1_test.go @@ -0,0 +1,677 @@ +// 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-present Datadog, Inc. + +package tracer + +import ( + "encoding/binary" + "io" + "testing" + "time" + + "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer/idx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestV1Payload_BasicFunctionality(t *testing.T) { + payload := newV1Payload(1.0) + + // Test initial state + assert.Equal(t, 0, payload.itemCount()) + assert.Equal(t, 0, payload.size()) + assert.Equal(t, 1.0, payload.protocol()) + + // Test clear + payload.clear() + assert.Equal(t, 0, payload.itemCount()) + assert.Equal(t, 0, payload.size()) +} + +func TestV1Payload_PushSingleSpan(t *testing.T) { + payload := newV1Payload(1.0) + + // Create a test span + span := &Span{ + name: "test.operation", + service: "test.service", + resource: "/test/resource", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, // 1ms + spanID: 12345, + traceID: 67890, + parentID: 0, + error: 0, + meta: map[string]string{ + "env": "test", + "version": "1.0.0", + "component": "test-component", + "kind": "server", + "custom": "value", + }, + metrics: map[string]float64{ + "custom.metric": 42.0, + "_sampling_priority_v1": 1.0, + }, + metaStruct: map[string]interface{}{ + "custom.struct": []byte("struct data"), + }, + spanLinks: []SpanLink{ + { + TraceID: 11111, + TraceIDHigh: 0, + SpanID: 22222, + Attributes: map[string]string{ + "link.attr": "link.value", + }, + Tracestate: "link-tracestate", + Flags: 0x01, + }, + }, + spanEvents: []spanEvent{ + { + Name: "test.event", + TimeUnixNano: uint64(time.Now().UnixNano()), + RawAttributes: map[string]interface{}{ + "event.attr": "event.value", + "event.number": 42, + "event.bool": true, + }, + }, + }, + } + + // Push the span + stats, err := payload.push(spanList{span}) + require.NoError(t, err) + + // Verify stats + assert.Equal(t, 1, stats.itemCount) + assert.Greater(t, stats.size, 0) + assert.Equal(t, 1, payload.itemCount()) + assert.Greater(t, payload.size(), 0) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + assert.Greater(t, len(data), 0) + + // Test unmarshaling with InternalTracerPayload + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify the unmarshaled payload structure + assert.NotNil(t, unmarshaledPayload.Strings) + assert.Len(t, unmarshaledPayload.Chunks, 1) + + chunk := unmarshaledPayload.Chunks[0] + assert.Len(t, chunk.Spans, 1) + assert.Equal(t, int32(1), chunk.Priority) + assert.False(t, chunk.DroppedTrace) + assert.Len(t, chunk.TraceID, 16) + + // Verify span data + internalSpan := chunk.Spans[0] + assert.Equal(t, "test.service", internalSpan.Service()) + assert.Equal(t, "test.operation", internalSpan.Name()) + assert.Equal(t, "/test/resource", internalSpan.Resource()) + assert.Equal(t, "web", internalSpan.Type()) + assert.Equal(t, uint64(12345), internalSpan.SpanID()) + assert.Equal(t, uint64(0), internalSpan.ParentID()) + assert.Equal(t, uint64(span.start), internalSpan.Start()) + assert.Equal(t, uint64(span.duration), internalSpan.Duration()) + assert.False(t, internalSpan.Error()) + assert.Equal(t, idx.SpanKind_SPAN_KIND_SERVER, internalSpan.Kind()) + + // Verify string references + assert.Equal(t, "test", internalSpan.Env()) + assert.Equal(t, "1.0.0", internalSpan.Version()) + assert.Equal(t, "test-component", internalSpan.Component()) + + // Verify attributes + attributes := internalSpan.Attributes() + assert.Len(t, attributes, 4) // custom, custom.metric, custom.struct, _sampling_priority_v1 + assert.Contains(t, attributes, unmarshaledPayload.Strings.Lookup("custom")) + assert.Contains(t, attributes, unmarshaledPayload.Strings.Lookup("custom.metric")) + assert.Contains(t, attributes, unmarshaledPayload.Strings.Lookup("custom.struct")) + assert.Contains(t, attributes, unmarshaledPayload.Strings.Lookup("_sampling_priority_v1")) + + // Verify span links + links := internalSpan.Links() + assert.Len(t, links, 1) + link := links[0] + assert.Len(t, link.TraceID(), 16) + assert.Equal(t, uint64(11111), binary.BigEndian.Uint64(link.TraceID()[8:])) + assert.Equal(t, uint64(22222), link.SpanID()) + assert.Equal(t, "link-tracestate", link.Tracestate()) + assert.Equal(t, uint32(0x01), link.Flags()) + assert.Len(t, link.Attributes(), 1) + + // Verify span events + events := internalSpan.Events() + assert.Len(t, events, 1) + event := events[0] + assert.Equal(t, "test.event", event.Name()) + // Note: Time and Attributes are not directly accessible through getter methods + // We can verify the event exists and has the correct name +} + +func TestV1Payload_PushMultipleSpans(t *testing.T) { + payload := newV1Payload(1.0) + + // Create multiple spans with the same trace ID + spans := make([]*Span, 3) + for i := 0; i < 3; i++ { + spans[i] = &Span{ + name: "test.operation", + service: "test.service", + resource: "/test/resource", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: uint64(1000 + i), + traceID: 12345, // Same trace ID + parentID: 0, + error: 0, + meta: map[string]string{ + "env": "test", + }, + } + } + + // Push all spans + stats, err := payload.push(spanList(spans)) + require.NoError(t, err) + + // Verify stats + assert.Equal(t, 1, stats.itemCount) // One chunk + assert.Greater(t, stats.size, 0) + assert.Equal(t, 1, payload.itemCount()) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify structure + assert.Len(t, unmarshaledPayload.Chunks, 1) + chunk := unmarshaledPayload.Chunks[0] + assert.Len(t, chunk.Spans, 3) + + // Verify all spans have the same trace ID + // Note: TraceID is not directly accessible through getter methods + // We can verify the chunk has the correct trace ID + assert.Len(t, chunk.TraceID, 16) +} + +func TestV1Payload_PushMultipleChunks(t *testing.T) { + payload := newV1Payload(1.0) + + // Create spans for different traces + trace1Spans := []*Span{ + { + name: "operation1", + service: "service1", + resource: "/resource1", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 1001, + traceID: 11111, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + }, + } + + trace2Spans := []*Span{ + { + name: "operation2", + service: "service2", + resource: "/resource2", + spanType: "db", + start: time.Now().UnixNano(), + duration: 2000000, + spanID: 2001, + traceID: 22222, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + }, + } + + // Push first chunk + stats1, err := payload.push(spanList(trace1Spans)) + require.NoError(t, err) + assert.Equal(t, 1, stats1.itemCount) + + // Push second chunk + stats2, err := payload.push(spanList(trace2Spans)) + require.NoError(t, err) + assert.Equal(t, 2, stats2.itemCount) + + // Verify total count + assert.Equal(t, 2, payload.itemCount()) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify structure + assert.Len(t, unmarshaledPayload.Chunks, 2) + + // Verify first chunk + chunk1 := unmarshaledPayload.Chunks[0] + assert.Len(t, chunk1.Spans, 1) + assert.Equal(t, "operation1", chunk1.Spans[0].Name()) + assert.Equal(t, "service1", chunk1.Spans[0].Service()) + + // Verify second chunk + chunk2 := unmarshaledPayload.Chunks[1] + assert.Len(t, chunk2.Spans, 1) + assert.Equal(t, "operation2", chunk2.Spans[0].Name()) + assert.Equal(t, "service2", chunk2.Spans[0].Service()) +} + +func TestV1Payload_EmptySpanList(t *testing.T) { + payload := newV1Payload(1.0) + + // Push empty span list + stats, err := payload.push(spanList{}) + require.NoError(t, err) + + // Should still create a chunk + assert.Equal(t, 1, stats.itemCount) + assert.Equal(t, 1, payload.itemCount()) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify structure + assert.Len(t, unmarshaledPayload.Chunks, 1) + chunk := unmarshaledPayload.Chunks[0] + assert.Len(t, chunk.Spans, 0) + assert.False(t, chunk.DroppedTrace) +} + +func TestV1Payload_ResetAndClear(t *testing.T) { + payload := newV1Payload(1.0) + + // Add some data + span := &Span{ + name: "test.operation", + service: "test.service", + resource: "/test/resource", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 12345, + traceID: 67890, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + } + + _, err := payload.push(spanList{span}) + require.NoError(t, err) + + // Test reset + payload.reset() + assert.Equal(t, 1, payload.itemCount()) // Count should remain + + // Test clear + payload.clear() + assert.Equal(t, 0, payload.itemCount()) + assert.Equal(t, 0, payload.size()) +} + +func TestV1Payload_ConcurrentAccess(t *testing.T) { + payload := newV1Payload(1.0) + + // Test concurrent writes (should be safe due to mutex) + done := make(chan bool, 2) + + go func() { + span := &Span{ + name: "operation1", + service: "service1", + resource: "/resource1", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 1001, + traceID: 11111, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + } + _, err := payload.push(spanList{span}) + assert.NoError(t, err) + done <- true + }() + + go func() { + span := &Span{ + name: "operation2", + service: "service2", + resource: "/resource2", + spanType: "db", + start: time.Now().UnixNano(), + duration: 2000000, + spanID: 2001, + traceID: 22222, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + } + _, err := payload.push(spanList{span}) + assert.NoError(t, err) + done <- true + }() + + // Wait for both goroutines to complete + <-done + <-done + + // Verify final state + assert.Equal(t, 2, payload.itemCount()) +} + +func TestV1Payload_StringInterning(t *testing.T) { + payload := newV1Payload(1.0) + + // Create spans with duplicate strings + spans := []*Span{ + { + name: "test.operation", + service: "test.service", + resource: "/test/resource", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 1001, + traceID: 11111, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test", "custom": "value"}, + }, + { + name: "test.operation", // Same name + service: "test.service", // Same service + resource: "/test/resource", // Same resource + spanType: "web", // Same type + start: time.Now().UnixNano(), + duration: 2000000, + spanID: 1002, + traceID: 11111, // Same trace + parentID: 1001, // Child of first span + error: 0, + meta: map[string]string{"env": "test", "custom": "value"}, // Same meta + }, + } + + // Push spans + _, err := payload.push(spanList(spans)) + require.NoError(t, err) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify string interning worked + assert.Len(t, unmarshaledPayload.Chunks, 1) + chunk := unmarshaledPayload.Chunks[0] + assert.Len(t, chunk.Spans, 2) + + // Both spans should have the same string values (string interning should work) + span1 := chunk.Spans[0] + span2 := chunk.Spans[1] + + // Verify the strings are actually the same + assert.Equal(t, "test.service", span1.Service()) + assert.Equal(t, "test.operation", span1.Name()) + assert.Equal(t, "/test/resource", span1.Resource()) + assert.Equal(t, "web", span1.Type()) + assert.Equal(t, "test", span1.Env()) + + // Verify both spans have the same values + assert.Equal(t, span1.Service(), span2.Service()) + assert.Equal(t, span1.Name(), span2.Name()) + assert.Equal(t, span1.Resource(), span2.Resource()) + assert.Equal(t, span1.Type(), span2.Type()) + assert.Equal(t, span1.Env(), span2.Env()) +} + +func TestV1Payload_ChunkAttributes(t *testing.T) { + payload := newV1Payload(1.0) + + // Create span with sampling mechanism + span := &Span{ + name: "test.operation", + service: "test.service", + resource: "/test/resource", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 12345, + traceID: 67890, + parentID: 0, + error: 0, + meta: map[string]string{ + "env": "test", + "_dd.p.dm": "123", // Sampling mechanism + "custom": "value", + }, + } + + // Push span + _, err := payload.push(spanList{span}) + require.NoError(t, err) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify chunk attributes + assert.Len(t, unmarshaledPayload.Chunks, 1) + chunk := unmarshaledPayload.Chunks[0] + assert.Equal(t, uint32(123), chunk.SamplingMechanism()) + + // Verify _dd.p.dm was not included in chunk attributes + found := false + for keyRef := range chunk.Attributes { + key := unmarshaledPayload.Strings.Get(keyRef) + if key == "_dd.p.dm" { + found = true + break + } + } + assert.False(t, found, "_dd.p.dm should not be in chunk attributes") + + // Verify custom attribute was included + found = false + for keyRef := range chunk.Attributes { + key := unmarshaledPayload.Strings.Get(keyRef) + if key == "custom" { + found = true + break + } + } + assert.True(t, found, "custom attribute should be in chunk attributes") +} + +func TestV1Payload_WriteAndGrow(t *testing.T) { + payload := newV1Payload(1.0) + + // Test Write method + testData := []byte("test data") + n, err := payload.Write(testData) + require.NoError(t, err) + assert.Equal(t, len(testData), n) + + // Test grow method + payload.grow(1000) + + // Test that we can still read the data + data, err := io.ReadAll(payload) + require.NoError(t, err) + assert.Equal(t, testData, data) +} + +func TestV1Payload_SamplingPriority(t *testing.T) { + payload := newV1Payload(1.0) + + // Test with sampling priority = 1 + span1 := &Span{ + name: "test.operation", + service: "test.service", + resource: "/test/resource", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 12345, + traceID: 67890, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + metrics: map[string]float64{ + "_sampling_priority_v1": 1.0, + }, + } + + // Push the span + _, err := payload.push(spanList{span1}) + require.NoError(t, err) + + // Test reading the payload + data, err := io.ReadAll(payload) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload idx.InternalTracerPayload + remaining, err := unmarshaledPayload.UnmarshalMsg(data) + require.NoError(t, err) + assert.Empty(t, remaining) + + // Verify the chunk has the correct priority + assert.Len(t, unmarshaledPayload.Chunks, 1) + chunk1 := unmarshaledPayload.Chunks[0] + assert.Equal(t, int32(1), chunk1.Priority) + + // Test with sampling priority = 0 + payload2 := newV1Payload(1.0) + span2 := &Span{ + name: "test.operation2", + service: "test.service", + resource: "/test/resource2", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 12346, + traceID: 67891, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + metrics: map[string]float64{ + "_sampling_priority_v1": 0.0, + }, + } + + // Push the span + _, err = payload2.push(spanList{span2}) + require.NoError(t, err) + + // Test reading the payload + data2, err := io.ReadAll(payload2) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload2 idx.InternalTracerPayload + remaining2, err := unmarshaledPayload2.UnmarshalMsg(data2) + require.NoError(t, err) + assert.Empty(t, remaining2) + + // Verify the chunk has the correct priority + assert.Len(t, unmarshaledPayload2.Chunks, 1) + chunk2 := unmarshaledPayload2.Chunks[0] + assert.Equal(t, int32(0), chunk2.Priority) + + // Test with no sampling priority (should default to 0) + payload3 := newV1Payload(1.0) + span3 := &Span{ + name: "test.operation3", + service: "test.service", + resource: "/test/resource3", + spanType: "web", + start: time.Now().UnixNano(), + duration: 1000000, + spanID: 12347, + traceID: 67892, + parentID: 0, + error: 0, + meta: map[string]string{"env": "test"}, + metrics: map[string]float64{}, // No sampling priority + } + + // Push the span + _, err = payload3.push(spanList{span3}) + require.NoError(t, err) + + // Test reading the payload + data3, err := io.ReadAll(payload3) + require.NoError(t, err) + + // Test unmarshaling + var unmarshaledPayload3 idx.InternalTracerPayload + remaining3, err := unmarshaledPayload3.UnmarshalMsg(data3) + require.NoError(t, err) + assert.Empty(t, remaining3) + + // Verify the chunk has the default priority + assert.Len(t, unmarshaledPayload3.Chunks, 1) + chunk3 := unmarshaledPayload3.Chunks[0] + assert.Equal(t, int32(0), chunk3.Priority) +} + +func TestV1Payload_Close(t *testing.T) { + payload := newV1Payload(1.0) + + // Close should not return an error + err := payload.Close() + assert.NoError(t, err) +} diff --git a/ddtrace/tracer/tracer_test.go b/ddtrace/tracer/tracer_test.go index 20b3531ae9..25fa6ac4d9 100644 --- a/ddtrace/tracer/tracer_test.go +++ b/ddtrace/tracer/tracer_test.go @@ -1238,7 +1238,7 @@ func TestTracerNoDebugStack(t *testing.T) { // newDefaultTransport return a default transport for this tracing client func newDefaultTransport() transport { - return newHTTPTransport(defaultURL, defaultHTTPClient(0, true)) + return newHTTPTransport(defaultURL, defaultHTTPClient(0, true), false) } func TestNewSpan(t *testing.T) { @@ -1357,7 +1357,7 @@ func TestTracerPrioritySampler(t *testing.T) { url := "http://" + srv.Listener.Addr().String() tr, _, flush, stop, err := startTestTracer(t, - withTransport(newHTTPTransport(url, defaultHTTPClient(0, false))), + withTransport(newHTTPTransport(url, defaultHTTPClient(0, false), false)), ) assert.Nil(err) defer stop() diff --git a/ddtrace/tracer/transport.go b/ddtrace/tracer/transport.go index 1046392dc9..e398551696 100644 --- a/ddtrace/tracer/transport.go +++ b/ddtrace/tracer/transport.go @@ -66,8 +66,9 @@ const ( traceCountHeader = "X-Datadog-Trace-Count" // header containing the number of traces in the payload obfuscationVersionHeader = "Datadog-Obfuscation-Version" // header containing the version of obfuscation used, if any - tracesAPIPath = "/v0.4/traces" - statsAPIPath = "/v0.6/stats" + tracesAPIPath = "/v0.4/traces" + tracesV1APIPath = "/v1.0/traces" + statsAPIPath = "/v0.6/stats" ) // transport is an interface for communicating data to the agent. @@ -87,6 +88,7 @@ type httpTransport struct { statsURL string // the delivery URL for stats client *http.Client // the HTTP client used in the POST headers map[string]string // the Transport headers + useV1 bool // whether to use v1.0/traces endpoint } // newTransport returns a new Transport implementation that sends traces to a @@ -96,7 +98,7 @@ type httpTransport struct { // running on a non-default port, if it's located on another machine, or when // otherwise needing to customize the transport layer, for instance when using // a unix domain socket. -func newHTTPTransport(url string, client *http.Client) *httpTransport { +func newHTTPTransport(url string, client *http.Client, useV1 bool) *httpTransport { // initialize the default EncoderPool with Encoder headers defaultHeaders := map[string]string{ "Datadog-Meta-Lang": "go", @@ -114,11 +116,16 @@ func newHTTPTransport(url string, client *http.Client) *httpTransport { if extEnv := internal.ExternalEnvironment(); extEnv != "" { defaultHeaders["Datadog-External-Env"] = extEnv } + tracePath := tracesAPIPath + if useV1 { + tracePath = tracesV1APIPath + } return &httpTransport{ - traceURL: fmt.Sprintf("%s%s", url, tracesAPIPath), + traceURL: fmt.Sprintf("%s%s", url, tracePath), statsURL: fmt.Sprintf("%s%s", url, statsAPIPath), client: client, headers: defaultHeaders, + useV1: useV1, } } diff --git a/ddtrace/tracer/transport_bench_test.go b/ddtrace/tracer/transport_bench_test.go index e8b4255b4b..19790ca645 100644 --- a/ddtrace/tracer/transport_bench_test.go +++ b/ddtrace/tracer/transport_bench_test.go @@ -22,7 +22,7 @@ func BenchmarkHTTPTransportSend(b *testing.B) { })) defer server.Close() - transport := newHTTPTransport(server.URL, defaultHTTPClient(5*time.Second, false)) + transport := newHTTPTransport(server.URL, defaultHTTPClient(5*time.Second, false), false) payloadSizes := []struct { name string @@ -67,7 +67,7 @@ func BenchmarkTransportSendConcurrent(b *testing.B) { })) defer server.Close() - transport := newHTTPTransport(server.URL, defaultHTTPClient(5*time.Second, false)) + transport := newHTTPTransport(server.URL, defaultHTTPClient(5*time.Second, false), false) concurrencyLevels := []int{1, 2, 4, 8} for _, concurrency := range concurrencyLevels { diff --git a/ddtrace/tracer/transport_test.go b/ddtrace/tracer/transport_test.go index 0bf5b67e9b..68068bc6e7 100644 --- a/ddtrace/tracer/transport_test.go +++ b/ddtrace/tracer/transport_test.go @@ -85,7 +85,7 @@ func TestTracesAgentIntegration(t *testing.T) { } for _, tc := range testCases { - transport := newHTTPTransport(defaultURL, defaultHTTPClient(0, false)) + transport := newHTTPTransport(defaultURL, defaultHTTPClient(0, false), false) p, err := encode(tc.payload) assert.NoError(err) body, err := transport.send(p) @@ -159,7 +159,7 @@ func TestTransportResponse(t *testing.T) { w.Write([]byte(tt.body)) })) defer srv.Close() - transport := newHTTPTransport(srv.URL, defaultHTTPClient(0, false)) + transport := newHTTPTransport(srv.URL, defaultHTTPClient(0, false), false) rc, err := transport.send(newPayload(traceProtocolV04)) if tt.err != "" { assert.Equal(tt.err, err.Error()) @@ -199,7 +199,7 @@ func TestTraceCountHeader(t *testing.T) { })) defer srv.Close() for _, tc := range testCases { - transport := newHTTPTransport(srv.URL, defaultHTTPClient(0, false)) + transport := newHTTPTransport(srv.URL, defaultHTTPClient(0, false), false) p, err := encode(tc.payload) assert.NoError(err) _, err = transport.send(p) @@ -241,7 +241,7 @@ func TestCustomTransport(t *testing.T) { c := &http.Client{} crt := wrapRecordingRoundTripper(c) - transport := newHTTPTransport(srv.URL, c) + transport := newHTTPTransport(srv.URL, c, false) p, err := encode(getTestTrace(1, 1)) assert.NoError(err) _, err = transport.send(p) diff --git a/ddtrace/tracer/writer.go b/ddtrace/tracer/writer.go index ea5c1c55b6..2e3ec7f8d4 100644 --- a/ddtrace/tracer/writer.go +++ b/ddtrace/tracer/writer.go @@ -98,7 +98,7 @@ func (h *agentTraceWriter) stop() { // newPayload returns a new payload based on the trace protocol. func (h *agentTraceWriter) newPayload() payload { - return newPayload(h.config.traceProtocol) + return newPayloadWithConfig(h.config.traceProtocol, h.config.v1Test) } // flush will push any currently buffered traces to the server. diff --git a/internal/env/supported_configurations.gen.go b/internal/env/supported_configurations.gen.go index 96317c308b..9b07cf0816 100644 --- a/internal/env/supported_configurations.gen.go +++ b/internal/env/supported_configurations.gen.go @@ -202,6 +202,7 @@ var SupportedConfigurations = map[string]struct{}{ "DD_TRACE_VAULT_ANALYTICS_ENABLED": {}, "DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH": {}, "DD_TRACE__ANALYTICS_ENABLED": {}, + "DD_V1_TEST": {}, "DD_VERSION": {}, "OTEL_LOGS_EXPORTER": {}, "OTEL_LOG_LEVEL": {}, diff --git a/internal/env/supported_configurations.json b/internal/env/supported_configurations.json index e592042560..cf15c855be 100644 --- a/internal/env/supported_configurations.json +++ b/internal/env/supported_configurations.json @@ -579,6 +579,9 @@ "DD_TRACE__ANALYTICS_ENABLED": [ "A" ], + "DD_V1_TEST": [ + "A" + ], "DD_VERSION": [ "A" ],