Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions enrichments/trace/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,28 @@ type ScopeConfig struct {
// ElasticTransactionConfig configures the enrichment attributes for the
// spans which are identified as elastic transaction.
type ElasticTransactionConfig struct {
ID AttributeConfig `mapstructure:"id"`
Root AttributeConfig `mapstructure:"root"`
Name AttributeConfig `mapstructure:"name"`
ProcessorEvent AttributeConfig `mapstructure:"processor_event"`
DurationUs AttributeConfig `mapstructure:"duration_us"`
Type AttributeConfig `mapstructure:"type"`
Result AttributeConfig `mapstructure:"result"`
EventOutcome AttributeConfig `mapstructure:"event_outcome"`
ID AttributeConfig `mapstructure:"id"`
Root AttributeConfig `mapstructure:"root"`
Name AttributeConfig `mapstructure:"name"`
ProcessorEvent AttributeConfig `mapstructure:"processor_event"`
RepresentativeCount AttributeConfig `mapstructure:"representative_count"`
DurationUs AttributeConfig `mapstructure:"duration_us"`
Type AttributeConfig `mapstructure:"type"`
Result AttributeConfig `mapstructure:"result"`
EventOutcome AttributeConfig `mapstructure:"event_outcome"`
}

// ElasticSpanConfig configures the enrichment attributes for the spans
// which are NOT identified as elastic transaction.
type ElasticSpanConfig struct {
Name AttributeConfig `mapstructure:"name"`
ProcessorEvent AttributeConfig `mapstructure:"processor_event"`
TypeSubtype AttributeConfig `mapstructure:"type_subtype"`
DurationUs AttributeConfig `mapstructure:"duration_us"`
EventOutcome AttributeConfig `mapstructure:"event_outcome"`
ServiceTarget AttributeConfig `mapstructure:"service_target"`
DestinationService AttributeConfig `mapstructure:"destination_service"`
Name AttributeConfig `mapstructure:"name"`
ProcessorEvent AttributeConfig `mapstructure:"processor_event"`
RepresentativeCount AttributeConfig `mapstructure:"representative_count"`
TypeSubtype AttributeConfig `mapstructure:"type_subtype"`
DurationUs AttributeConfig `mapstructure:"duration_us"`
EventOutcome AttributeConfig `mapstructure:"event_outcome"`
ServiceTarget AttributeConfig `mapstructure:"service_target"`
DestinationService AttributeConfig `mapstructure:"destination_service"`
}

// AttributeConfig is the configuration options for each attribute.
Expand All @@ -79,23 +81,25 @@ func Enabled() Config {
ServiceFrameworkVersion: AttributeConfig{Enabled: true},
},
Transaction: ElasticTransactionConfig{
ID: AttributeConfig{Enabled: true},
Root: AttributeConfig{Enabled: true},
Name: AttributeConfig{Enabled: true},
ProcessorEvent: AttributeConfig{Enabled: true},
DurationUs: AttributeConfig{Enabled: true},
Type: AttributeConfig{Enabled: true},
Result: AttributeConfig{Enabled: true},
EventOutcome: AttributeConfig{Enabled: true},
ID: AttributeConfig{Enabled: true},
Root: AttributeConfig{Enabled: true},
Name: AttributeConfig{Enabled: true},
ProcessorEvent: AttributeConfig{Enabled: true},
DurationUs: AttributeConfig{Enabled: true},
Type: AttributeConfig{Enabled: true},
Result: AttributeConfig{Enabled: true},
EventOutcome: AttributeConfig{Enabled: true},
RepresentativeCount: AttributeConfig{Enabled: true},
},
Span: ElasticSpanConfig{
Name: AttributeConfig{Enabled: true},
ProcessorEvent: AttributeConfig{Enabled: true},
TypeSubtype: AttributeConfig{Enabled: true},
DurationUs: AttributeConfig{Enabled: true},
EventOutcome: AttributeConfig{Enabled: true},
ServiceTarget: AttributeConfig{Enabled: true},
DestinationService: AttributeConfig{Enabled: true},
Name: AttributeConfig{Enabled: true},
ProcessorEvent: AttributeConfig{Enabled: true},
TypeSubtype: AttributeConfig{Enabled: true},
DurationUs: AttributeConfig{Enabled: true},
EventOutcome: AttributeConfig{Enabled: true},
ServiceTarget: AttributeConfig{Enabled: true},
DestinationService: AttributeConfig{Enabled: true},
RepresentativeCount: AttributeConfig{Enabled: true},
},
}
}
2 changes: 2 additions & 0 deletions enrichments/trace/internal/elastic/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
AttributeTransactionType = "transaction.type"
AttributeTransactionDurationUs = "transaction.duration.us"
AttributeTransactionResult = "transaction.result"
AttributeTransactionRepresentativeCount = "transaction.representative_count"
AttributeSpanName = "span.name"
AttributeSpanType = "span.type"
AttributeSpanSubtype = "span.subtype"
Expand All @@ -43,4 +44,5 @@ const (
AttributeServiceTargetName = "service.target.name"
AttributeSpanDestinationServiceResource = "span.destination.service.resource"
AttributeSpanDurationUs = "span.duration.us"
AttributeSpanRepresentativeCount = "span.representative_count"
)
64 changes: 64 additions & 0 deletions enrichments/trace/internal/elastic/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ package elastic

import (
"fmt"
"math"
"net"
"net/http"
"net/url"
"strconv"
"strings"

"github.com/elastic/opentelemetry-lib/enrichments/trace/config"
"go.opentelemetry.io/collector/pdata/pcommon"
Expand Down Expand Up @@ -173,6 +175,10 @@ func (s *spanEnrichmentContext) enrichTransaction(
if cfg.ProcessorEvent.Enabled {
span.Attributes().PutStr(AttributeProcessorEvent, "transaction")
}
if cfg.RepresentativeCount.Enabled {
repCount := getRepresentativeCount(span.TraceState().AsRaw())
span.Attributes().PutDouble(AttributeTransactionRepresentativeCount, repCount)
}
if cfg.DurationUs.Enabled {
span.Attributes().PutInt(AttributeTransactionDurationUs, getDurationUs(span))
}
Expand All @@ -197,6 +203,10 @@ func (s *spanEnrichmentContext) enrichSpan(
if cfg.ProcessorEvent.Enabled {
span.Attributes().PutStr(AttributeProcessorEvent, "span")
}
if cfg.RepresentativeCount.Enabled {
repCount := getRepresentativeCount(span.TraceState().AsRaw())
span.Attributes().PutDouble(AttributeSpanRepresentativeCount, repCount)
}
if cfg.TypeSubtype.Enabled {
s.setSpanTypeSubtype(span)
}
Expand Down Expand Up @@ -391,6 +401,37 @@ func (s *spanEnrichmentContext) setDestinationService(span ptrace.Span) {
}
}

// getRepresentativeCount returns the number of spans represented by an
// individually sampled span as per the passed tracestate header.
//
// Representative count is similar to the OTel adjusted count definition
// with a difference that representative count can also include
// dynamically calculated representivity for non-probabilistic sampling.
// In addition, the representative count defaults to 1 if the adjusted
// count is UNKNOWN or the p-value is invalid.
//
// Def: https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/#adjusted-count)
//
// The count is calculated by using p-value:
// https://opentelemetry.io/docs/reference/specification/trace/tracestate-probability-sampling/#p-value
func getRepresentativeCount(tracestate string) float64 {
var p uint64
otValue := getValueForKeyInString(tracestate, "ot", ',', '=')
if otValue != "" {
pValue := getValueForKeyInString(otValue, "p", ';', ':')

if pValue != "" {
p, _ = strconv.ParseUint(pValue, 10, 6)
}
}

if p == 63 {
// p-value == 63 represents zero adjusted count
return 0.0
}
return math.Pow(2, float64(p))
Comment on lines +428 to +432
Copy link
Contributor Author

Choose a reason for hiding this comment

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

[For reviewers] This is slightly different to what we do in apm-data (ref). In apm-data, the comment says that if p-value is invalid then it should have 1 rep count however, it's not true as a p-value==64 will result in rep count of 0.

Copy link
Member

Choose a reason for hiding this comment

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

it's not true as a p-value==64 will result in rep count of 0.

I assume you meant p==63 as stated in https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/#p-value

Copy link
Contributor Author

@lahsivjar lahsivjar Aug 13, 2024

Choose a reason for hiding this comment

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

I assume you meant p==63 as stated in

Not quite. A p-value of 63 is a valid value, it represents a zero adjusted count (as per the specs you linked). OTOH, a p-value > 63 is invalid and should result in a rep count == 1 as per the comments in apm-data but the actual code in apm-data would give a rep count of 0 with p-value > 63

Copy link
Member

Choose a reason for hiding this comment

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

Got it. Do you consider that a bug in apm-data?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly, the specs are a bit muddy around this so I don't think it would be a bug.

}

func getDurationUs(span ptrace.Span) int64 {
return int64(span.EndTimestamp()-span.StartTimestamp()) / 1000
}
Expand All @@ -414,6 +455,29 @@ func isElasticTransaction(span ptrace.Span) bool {
return false
}

// parses string format `<key>=val<seperator>`
func getValueForKeyInString(str string, key string, separator rune, assignChar rune) string {
for {
str = strings.TrimSpace(str)
if str == "" {
break
}
kv := str
if sepIdx := strings.IndexRune(str, separator); sepIdx != -1 {
kv = strings.TrimSpace(str[:sepIdx])
str = str[sepIdx+1:]
} else {
str = ""
}
equal := strings.IndexRune(kv, assignChar)
if equal != -1 && kv[:equal] == key {
return kv[equal+1:]
}
}

return ""
}

// getHostPort derives the host:port value from url.* attributes. Unlike
// apm-data, the current code does NOT fallback to net.* or http.*
// attributes as most of these are now deprecated.
Expand Down
Loading