Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func TestDecodeMapToTransactionModel(t *testing.T) {
exceptions := func(key string) bool {
for _, s := range []string{
// values not set for RUM v3
"Kind", "representative_count", "message", "dropped_spans_stats",
"Kind", "representative_count", "message", "dropped_spans_stats", "profiler_stack_trace_ids",
// Not set for transaction events:
"AggregatedDuration",
"AggregatedDuration.Count",
Expand Down
15 changes: 15 additions & 0 deletions input/elasticapm/internal/modeldecoder/v2/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ func TestDecodeMapToTransactionModel(t *testing.T) {
"representative_count",
// Kind is tested further down
"Kind",
// profiler_stack_trace_ids are supplied as OTel-attributes
"profiler_stack_trace_ids",

// Not set for transaction events, tested in metricset decoding:
"AggregatedDuration",
Expand Down Expand Up @@ -636,6 +638,19 @@ func TestDecodeMapToTransactionModel(t *testing.T) {
mapToTransactionModel(&input, &event)
assert.Equal(t, "CLIENT", event.Span.Kind)
})

t.Run("elastic-profiling-ids", func(t *testing.T) {
var input transaction
var event modelpb.APMEvent
modeldecodertest.SetStructValues(&input, modeldecodertest.DefaultValues())
attrs := map[string]interface{}{
"elastic.profiler_stack_trace_ids": []interface{}{"id1", "id2"},
}
input.OTel.Attributes = attrs

mapToTransactionModel(&input, &event)
assert.Equal(t, []string{"id1", "id2"}, event.Transaction.ProfilerStackTraceIds)
})
})
t.Run("labels", func(t *testing.T) {
var input transaction
Expand Down
15 changes: 14 additions & 1 deletion input/otlp/traces.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"math"
"net"
"net/url"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -275,7 +276,19 @@ func TranslateTransaction(
k := replaceDots(kDots)
switch v.Type() {
case pcommon.ValueTypeSlice:
setLabel(k, event, ifaceAttributeValue(v))
switch kDots {
case "elastic.profiler_stack_trace_ids":
var vSlice = v.Slice()
event.Transaction.ProfilerStackTraceIds = slices.Grow(event.Transaction.ProfilerStackTraceIds, vSlice.Len())
for i := 0; i < vSlice.Len(); i++ {
var idVal = vSlice.At(i)
if idVal.Type() == pcommon.ValueTypeStr {
event.Transaction.ProfilerStackTraceIds = append(event.Transaction.ProfilerStackTraceIds, idVal.Str())
}
}
default:
setLabel(k, event, ifaceAttributeValue(v))
}
case pcommon.ValueTypeBool:
setLabel(k, event, ifaceAttributeValue(v))
case pcommon.ValueTypeDouble:
Expand Down
20 changes: 20 additions & 0 deletions input/otlp/traces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,26 @@ func TestArrayLabels(t *testing.T) {
}, modelpb.NumericLabels(spanEvent.NumericLabels))
}

func TestProfilerStackTraceIds(t *testing.T) {
validIds := []interface{}{"myId1", "myId2"}
badValueTypes := []interface{}{42, 68, "valid"}

tx1 := transformTransactionWithAttributes(t, map[string]interface{}{
"elastic.profiler_stack_trace_ids": validIds,
})
assert.Equal(t, []string{"myId1", "myId2"}, tx1.Transaction.ProfilerStackTraceIds)

tx2 := transformTransactionWithAttributes(t, map[string]interface{}{
"elastic.profiler_stack_trace_ids": badValueTypes,
})
assert.Equal(t, []string{"valid"}, tx2.Transaction.ProfilerStackTraceIds)

tx3 := transformTransactionWithAttributes(t, map[string]interface{}{
"elastic.profiler_stack_trace_ids": "bad type",
})
assert.Equal(t, []string(nil), tx3.Transaction.ProfilerStackTraceIds)
}

func TestConsumeTracesExportTimestamp(t *testing.T) {
traces, otelSpans := newTracesSpans()

Expand Down
17 changes: 17 additions & 0 deletions model/modeljson/internal/marshal_fastjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 16 additions & 15 deletions model/modeljson/internal/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,22 @@
package modeljson

type Transaction struct {
SpanCount SpanCount `json:"span_count,omitempty"`
UserExperience *UserExperience `json:"experience,omitempty"`
Custom KeyValueSlice `json:"custom,omitempty"`
Marks map[string]map[string]float64 `json:"marks,omitempty"`
Message *Message `json:"message,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Result string `json:"result,omitempty"`
ID string `json:"id,omitempty"`
DurationHistogram Histogram `json:"duration.histogram,omitempty"`
DroppedSpansStats []DroppedSpanStats `json:"dropped_spans_stats,omitempty"`
DurationSummary SummaryMetric `json:"duration.summary,omitempty"`
RepresentativeCount float64 `json:"representative_count,omitempty"`
Sampled bool `json:"sampled,omitempty"`
Root bool `json:"root,omitempty"`
SpanCount SpanCount `json:"span_count,omitempty"`
UserExperience *UserExperience `json:"experience,omitempty"`
Custom KeyValueSlice `json:"custom,omitempty"`
Marks map[string]map[string]float64 `json:"marks,omitempty"`
Message *Message `json:"message,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Result string `json:"result,omitempty"`
ID string `json:"id,omitempty"`
DurationHistogram Histogram `json:"duration.histogram,omitempty"`
DroppedSpansStats []DroppedSpanStats `json:"dropped_spans_stats,omitempty"`
ProfilerStackTraceIds []string `json:"profiler_stack_trace_ids,omitempty"`
DurationSummary SummaryMetric `json:"duration.summary,omitempty"`
RepresentativeCount float64 `json:"representative_count,omitempty"`
Sampled bool `json:"sampled,omitempty"`
Root bool `json:"root,omitempty"`
}

type SpanCount struct {
Expand Down
15 changes: 8 additions & 7 deletions model/modeljson/transaction.pb.json.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ import (

func TransactionModelJSON(e *modelpb.Transaction, out *modeljson.Transaction, metricset bool) {
*out = modeljson.Transaction{
ID: e.Id,
Type: e.Type,
Name: e.Name,
Result: e.Result,
Sampled: e.Sampled,
Root: e.Root,
RepresentativeCount: e.RepresentativeCount,
ID: e.Id,
Type: e.Type,
Name: e.Name,
Result: e.Result,
Sampled: e.Sampled,
Root: e.Root,
RepresentativeCount: e.RepresentativeCount,
ProfilerStackTraceIds: e.ProfilerStackTraceIds,
}

if e.Custom != nil {
Expand Down
21 changes: 12 additions & 9 deletions model/modeljson/transaction.pb.json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ func TestTransactionToModelJSON(t *testing.T) {
Count: 6,
Sum: 7,
},
RepresentativeCount: 8,
Sampled: true,
Root: true,
RepresentativeCount: 8,
Sampled: true,
Root: true,
ProfilerStackTraceIds: []string{"foo", "foo", "bar"},
},
expectedNoMetricset: &modeljson.Transaction{
SpanCount: modeljson.SpanCount{
Expand All @@ -134,9 +135,10 @@ func TestTransactionToModelJSON(t *testing.T) {
Count: 6,
Sum: 7,
},
RepresentativeCount: 8,
Sampled: true,
Root: true,
RepresentativeCount: 8,
Sampled: true,
Root: true,
ProfilerStackTraceIds: []string{"foo", "foo", "bar"},
},
expectedMetricset: &modeljson.Transaction{
SpanCount: modeljson.SpanCount{
Expand Down Expand Up @@ -174,9 +176,10 @@ func TestTransactionToModelJSON(t *testing.T) {
Count: 6,
Sum: 7,
},
RepresentativeCount: 8,
Sampled: true,
Root: true,
RepresentativeCount: 8,
Sampled: true,
Root: true,
ProfilerStackTraceIds: []string{"foo", "foo", "bar"},
},
},
}
Expand Down
Loading