Skip to content

Commit

Permalink
model/modeldecoder/v2: add span links
Browse files Browse the repository at this point in the history
  • Loading branch information
axw committed Mar 16, 2022
1 parent cc7df3d commit 57009d8
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 6 deletions.
1 change: 1 addition & 0 deletions changelogs/head.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ https://github.com/elastic/apm-server/compare/8.1\...main[View commits]
[float]
==== Intake API Changes
- Support for `faas.name` and `faas.version` added to intake and transaction metrics {pull}7427[7427]
- Updated intake v2 with support for `links` in `transaction` and `span` events {pull}7553[7553]

[float]
==== Added
Expand Down
27 changes: 27 additions & 0 deletions docs/spec/v2/span.json
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,33 @@
"type": "string",
"maxLength": 1024
},
"links": {
"description": "Links holds links to other spans, potentially in other traces.",
"type": [
"null",
"array"
],
"items": {
"type": "object",
"properties": {
"span_id": {
"description": "SpanID holds the ID of the linked span.",
"type": "string",
"maxLength": 1024
},
"trace_id": {
"description": "TraceID holds the ID of the linked span's trace.",
"type": "string",
"maxLength": 1024
}
},
"required": [
"span_id",
"trace_id"
]
},
"minItems": 0
},
"name": {
"description": "Name is the generic designation of a span in the scope of a transaction.",
"type": "string",
Expand Down
27 changes: 27 additions & 0 deletions docs/spec/v2/transaction.json
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,33 @@
"type": "string",
"maxLength": 1024
},
"links": {
"description": "Links holds links to other spans, potentially in other traces.",
"type": [
"null",
"array"
],
"items": {
"type": "object",
"properties": {
"span_id": {
"description": "SpanID holds the ID of the linked span.",
"type": "string",
"maxLength": 1024
},
"trace_id": {
"description": "TraceID holds the ID of the linked span's trace.",
"type": "string",
"maxLength": 1024
}
},
"required": [
"span_id",
"trace_id"
]
},
"minItems": 0
},
"marks": {
"description": "Marks capture the timing of a significant event during the lifetime of a transaction. Marks are organized into groups and can be set by the user or the agent. Marks are only reported by RUM agents.",
"type": [
Expand Down
20 changes: 20 additions & 0 deletions model/modeldecoder/v2/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,9 @@ func mapToSpanModel(from *span, event *model.APMEvent) {
if from.OTel.IsSet() {
mapOTelAttributesSpan(from.OTel, event)
}
if len(from.Links) > 0 {
mapSpanLinks(from.Links, &out.Links)
}
}

func mapToStracktraceModel(from []stacktraceFrame, out model.Stacktrace) {
Expand Down Expand Up @@ -1240,6 +1243,13 @@ func mapToTransactionModel(from *transaction, event *model.APMEvent) {
}
mapOTelAttributesTransaction(from.OTel, event)
}

if len(from.Links) > 0 {
if event.Span == nil {
event.Span = &model.Span{}
}
mapSpanLinks(from.Links, &event.Span.Links)
}
}

func mapOTelAttributesTransaction(from otel, out *model.APMEvent) {
Expand Down Expand Up @@ -1402,3 +1412,13 @@ func isOTelDoubleAttribute(k string) bool {
}
return false
}

func mapSpanLinks(from []spanLink, out *[]model.SpanLink) {
*out = make([]model.SpanLink, len(from))
for i, link := range from {
(*out)[i] = model.SpanLink{
Span: model.Span{ID: link.SpanID.Val},
Trace: model.Trace{ID: link.TraceID.Val},
}
}
}
15 changes: 14 additions & 1 deletion model/modeldecoder/v2/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,10 @@ type span struct {
// Type holds the span's type, and can have specific keywords
// within the service's domain (eg: 'request', 'backgroundjob', etc)
Type nullable.String `json:"type" validate:"required,maxLength=1024"`
_ struct{} `validate:"requiredAnyOf=start;timestamp"`
// Links holds links to other spans, potentially in other traces.
Links []spanLink `json:"links"`

_ struct{} `validate:"requiredAnyOf=start;timestamp"`
}

type spanContext struct {
Expand Down Expand Up @@ -930,6 +933,8 @@ type transaction struct {
// UserExperience holds metrics for measuring real user experience.
// This information is only sent by RUM agents.
UserExperience transactionUserExperience `json:"experience"`
// Links holds links to other spans, potentially in other traces.
Links []spanLink `json:"links"`
}

type otel struct {
Expand Down Expand Up @@ -1031,3 +1036,11 @@ type transactionDroppedSpansDurationSum struct {
// Us represents the summation of the span duration.
Us nullable.Int `json:"us" validate:"min=0"`
}

type spanLink struct {
// SpanID holds the ID of the linked span.
SpanID nullable.String `json:"span_id" validate:"required,maxLength=1024"`

// TraceID holds the ID of the linked span's trace.
TraceID nullable.String `json:"trace_id" validate:"required,maxLength=1024"`
}
50 changes: 48 additions & 2 deletions model/modeldecoder/v2/model_generated.go

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

4 changes: 4 additions & 0 deletions model/modeldecoder/v2/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,8 @@ func TestSpanRequiredValidationRules(t *testing.T) {
"composite.count": nil,
"composite.sum": nil,
"composite.compression_strategy": nil,
"links.span_id": nil,
"links.trace_id": nil,
}
cb := assertRequiredFn(t, requiredKeys, event.validate)
modeldecodertest.SetZeroStructValue(&event, cb)
Expand Down Expand Up @@ -557,6 +559,8 @@ func TestTransactionRequiredValidationRules(t *testing.T) {
"experience.longtask.sum": nil,
"experience.longtask.max": nil,
"session.id": nil,
"links.span_id": nil,
"links.trace_id": nil,
}
cb := assertRequiredFn(t, requiredKeys, event.validate)
modeldecodertest.SetZeroStructValue(&event, cb)
Expand Down
28 changes: 27 additions & 1 deletion model/modeldecoder/v2/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/elastic/apm-server/model"
"github.com/elastic/apm-server/model/modeldecoder"
"github.com/elastic/apm-server/model/modeldecoder/modeldecodertest"
"github.com/elastic/apm-server/model/modeldecoder/nullable"
)

func TestResetSpanOnRelease(t *testing.T) {
Expand Down Expand Up @@ -94,7 +95,6 @@ func TestDecodeMapToSpanModel(t *testing.T) {
"Kind",

// Not set for spans:
"Links",
"DestinationService.ResponseTime",
"DestinationService.ResponseTime.Count",
"DestinationService.ResponseTime.Sum",
Expand All @@ -112,6 +112,10 @@ func TestDecodeMapToSpanModel(t *testing.T) {
return true
}
}
if strings.HasPrefix(key, "Links") {
// Links are tested below in the 'links' test.
return true
}
return false
}

Expand Down Expand Up @@ -446,6 +450,7 @@ func TestDecodeMapToSpanModel(t *testing.T) {
assert.Equal(t, "CONSUMER", event.Span.Kind)
})
})

t.Run("labels", func(t *testing.T) {
var input span
input.Context.Tags = common.MapStr{
Expand All @@ -465,4 +470,25 @@ func TestDecodeMapToSpanModel(t *testing.T) {
"d": {Value: float64(12315124131.12315124131)},
}, out.NumericLabels)
})

t.Run("links", func(t *testing.T) {
var input span
input.Links = []spanLink{{
SpanID: nullable.String{Val: "span1"},
TraceID: nullable.String{Val: "trace1"},
}, {
SpanID: nullable.String{Val: "span2"},
TraceID: nullable.String{Val: "trace2"},
}}
var out model.APMEvent
mapToSpanModel(&input, &out)

assert.Equal(t, []model.SpanLink{{
Span: model.Span{ID: "span1"},
Trace: model.Trace{ID: "trace1"},
}, {
Span: model.Span{ID: "span2"},
Trace: model.Trace{ID: "trace2"},
}}, out.Span.Links)
})
}
25 changes: 23 additions & 2 deletions model/modeldecoder/v2/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/elastic/apm-server/model"
"github.com/elastic/apm-server/model/modeldecoder"
"github.com/elastic/apm-server/model/modeldecoder/modeldecodertest"
"github.com/elastic/apm-server/model/modeldecoder/nullable"
"github.com/elastic/beats/v7/libbeat/common"
)

Expand Down Expand Up @@ -568,15 +569,15 @@ func TestDecodeMapToTransactionModel(t *testing.T) {
})
})
t.Run("labels", func(t *testing.T) {
var input span
var input transaction
input.Context.Tags = common.MapStr{
"a": "b",
"c": float64(12315124131),
"d": 12315124131.12315124131,
"e": true,
}
var out model.APMEvent
mapToSpanModel(&input, &out)
mapToTransactionModel(&input, &out)
assert.Equal(t, model.Labels{
"a": {Value: "b"},
"e": {Value: "true"},
Expand All @@ -586,4 +587,24 @@ func TestDecodeMapToTransactionModel(t *testing.T) {
"d": {Value: float64(12315124131.12315124131)},
}, out.NumericLabels)
})
t.Run("links", func(t *testing.T) {
var input transaction
input.Links = []spanLink{{
SpanID: nullable.String{Val: "span1"},
TraceID: nullable.String{Val: "trace1"},
}, {
SpanID: nullable.String{Val: "span2"},
TraceID: nullable.String{Val: "trace2"},
}}
var out model.APMEvent
mapToTransactionModel(&input, &out)

assert.Equal(t, []model.SpanLink{{
Span: model.Span{ID: "span1"},
Trace: model.Trace{ID: "trace1"},
}, {
Span: model.Span{ID: "span2"},
Trace: model.Trace{ID: "trace2"},
}}, out.Span.Links)
})
}
3 changes: 3 additions & 0 deletions processor/stream/processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ func TestIntegrationESOutput(t *testing.T) {
}, {
name: "OpenTelemetryBridge",
path: "otel-bridge.ndjson",
}, {
name: "SpanLinks",
path: "span-links.ndjson",
}, {
name: "InvalidEvent",
path: "invalid-event.ndjson",
Expand Down
Loading

0 comments on commit 57009d8

Please sign in to comment.