Skip to content

Commit

Permalink
Add representative count enrichment (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahsivjar authored Aug 13, 2024
1 parent 25c1f83 commit 40b4fd9
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 124 deletions.
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))
}

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

0 comments on commit 40b4fd9

Please sign in to comment.