Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add span links to the SDK and SDK probe #1135

Merged
merged 6 commits into from
Oct 3, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ func (c *converter) convertEvent(e *event) []*probe.SpanEvent {
TracerSchema: ss.SchemaUrl(),
Kind: spanKind(span.Kind()),
Attributes: attributes(span.Attributes()),
Links: c.links(span.Links()),
Status: status(span.Status()),
// TODO: Events.
// TODO: Links.
}}
}

Expand All @@ -179,6 +179,35 @@ func spanKind(kind ptrace.SpanKind) trace.SpanKind {
}
}

func (c *converter) links(links ptrace.SpanLinkSlice) []trace.Link {
n := links.Len()
if n == 0 {
return nil
}

out := make([]trace.Link, n)
for i := range out {
l := links.At(i)

raw := l.TraceState().AsRaw()
ts, err := trace.ParseTraceState(raw)
if err != nil {
c.logger.Error("failed to parse link tracestate", "error", err, "tracestate", raw)
}

out[i] = trace.Link{
SpanContext: trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID(l.TraceID()),
SpanID: trace.SpanID(l.SpanID()),
TraceFlags: trace.TraceFlags(l.Flags()),
TraceState: ts,
}),
Attributes: attributes(l.Attributes()),
}
}
return out
}

func attributes(m pcommon.Map) []attribute.KeyValue {
out := make([]attribute.KeyValue, 0, m.Len())
m.Range(func(key string, val pcommon.Value) bool {
Expand Down
1 change: 1 addition & 0 deletions internal/pkg/instrumentation/probe/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ type SpanEvent struct {
TracerName string
TracerVersion string
TracerSchema string
Links []trace.Link
}
4 changes: 3 additions & 1 deletion internal/pkg/opentelemetry/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ func (c *Controller) Trace(event *probe.Event) {
Start(ctx, se.SpanName,
trace.WithAttributes(se.Attributes...),
trace.WithSpanKind(kind),
trace.WithTimestamp(se.StartTime))
trace.WithTimestamp(se.StartTime),
trace.WithLinks(se.Links...),
)
span.SetStatus(se.Status.Code, se.Status.Description)
span.End(trace.WithTimestamp(se.EndTime))
}
Expand Down
44 changes: 39 additions & 5 deletions internal/test/e2e/autosdk/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import (
"go.opentelemetry.io/auto/sdk"
)

const pkgName = "go.opentelemetry.io/auto/internal/test/e2e/autosdk"
const (
pkgName = "go.opentelemetry.io/auto/internal/test/e2e/autosdk"
pkgVer = "v1.23.42"
schemaURL = "https://some_schema"
)

// Y2K (January 1, 2000).
var y2k = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
Expand All @@ -26,7 +30,7 @@ type app struct {
tracer trace.Tracer
}

func (a *app) Run(ctx context.Context, user string, admin bool) error {
func (a *app) Run(ctx context.Context, user string, admin bool, in <-chan msg) error {
opts := []trace.SpanStartOption{
trace.WithAttributes(
attribute.String("user", user),
Expand All @@ -38,18 +42,48 @@ func (a *app) Run(ctx context.Context, user string, admin bool) error {
_, span := a.tracer.Start(ctx, "Run", opts...)
defer span.End(trace.WithTimestamp(y2k.Add(1 * time.Second)))

for m := range in {
span.AddLink(trace.Link{
SpanContext: m.SpanContext,
Attributes: []attribute.KeyValue{attribute.String("data", m.Data)},
})
}

return errors.New("broken")
}

type msg struct {
SpanContext trace.SpanContext
Data string
}

func sig(ctx context.Context) <-chan msg {
tracer := trace.SpanFromContext(ctx).TracerProvider().Tracer(
pkgName,
trace.WithInstrumentationVersion(pkgVer),
trace.WithSchemaURL(schemaURL),
)

ts := y2k.Add(10 * time.Microsecond)
_, span := tracer.Start(ctx, "sig", trace.WithTimestamp(ts))
defer span.End(trace.WithTimestamp(ts.Add(100 * time.Microsecond)))

out := make(chan msg, 1)
out <- msg{SpanContext: span.SpanContext(), Data: "Hello World"}
close(out)

return out
}

func main() {
// give time for auto-instrumentation to start up
time.Sleep(5 * time.Second)

provider := sdk.GetTracerProvider()
tracer := provider.Tracer(
pkgName,
trace.WithInstrumentationVersion("v1.23.42"),
trace.WithSchemaURL("https://some_schema"),
trace.WithInstrumentationVersion(pkgVer),
trace.WithSchemaURL(schemaURL),
)
app := app{tracer: tracer}

Expand All @@ -59,7 +93,7 @@ func main() {
ctx, span := tracer.Start(ctx, "main", trace.WithTimestamp(y2k))
defer span.End(trace.WithTimestamp(y2k.Add(5 * time.Second)))

err := app.Run(ctx, "Alice", true)
err := app.Run(ctx, "Alice", true, sig(ctx))
if err != nil {
span.SetStatus(codes.Error, "application error")
span.RecordError(
Expand Down
26 changes: 26 additions & 0 deletions internal/test/e2e/autosdk/traces.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@
"version": "v1.23.42"
},
"spans": [
{
"traceId": "xxxxx",
"spanId": "xxxxx",
"parentSpanId": "xxxxx",
"flags": 256,
"name": "sig",
"kind": 3,
"startTimeUnixNano": "946684800000010000",
"endTimeUnixNano": "946684800000110000",
"status": {}
}
{
"traceId": "xxxxx",
"spanId": "xxxxx",
Expand All @@ -63,6 +74,21 @@
"kind": 1,
"startTimeUnixNano": "946684800500000000",
"endTimeUnixNano": "946684801000000000",
"links": [
{
"traceId": "xxxxx",
"spanId": "xxxxx",
"attributes": [
{
"key": "data",
"value": {
"stringValue": "Hello World"
}
}
],
"flags": 256
}
]
"attributes": [
{
"key": "user",
Expand Down
42 changes: 42 additions & 0 deletions internal/test/e2e/autosdk/verify.bats
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ SCOPE="go.opentelemetry.io/auto/internal/test/e2e/autosdk"
assert_equal "$(echo $status | jq ".message")" '"application error"'
}

@test "autosdk :: sig span :: trace ID" {
trace_id=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".traceId")
assert_regex "$trace_id" ${MATCH_A_TRACE_ID}
}

@test "autosdk :: sig span :: span ID" {
trace_id=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".spanId")
assert_regex "$trace_id" ${MATCH_A_SPAN_ID}
}

@test "autosdk :: sig span :: parent span ID" {
parent_span_id=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".parentSpanId")
assert_regex "$parent_span_id" ${MATCH_A_SPAN_ID}
}

@test "autosdk :: sig span :: start time" {
timestamp=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".startTimeUnixNano")
assert_regex "$timestamp" "946684800000010000"
}

@test "autosdk :: sig span :: end time" {
timestamp=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".endTimeUnixNano")
assert_regex "$timestamp" "946684800000110000"
}

@test "autosdk :: Run span :: trace ID" {
trace_id=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"Run\")" | jq ".traceId")
assert_regex "$trace_id" ${MATCH_A_TRACE_ID}
Expand Down Expand Up @@ -94,3 +119,20 @@ SCOPE="go.opentelemetry.io/auto/internal/test/e2e/autosdk"
result=$(span_attributes_for ${SCOPE} | jq "select(.key == \"admin\").value.boolValue")
assert_equal "$result" 'true'
}

@test "autosdk :: Run span :: link :: traceID" {
want=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".traceId")
got=$(span_links ${SCOPE} "Run" | jq ".traceId")
assert_equal "$got" "$want"
}

@test "autosdk :: Run span :: link :: spanID" {
want=$(spans_from_scope_named ${SCOPE} | jq "select(.name == \"sig\")" | jq ".spanId")
got=$(span_links ${SCOPE} "Run" | jq ".spanId")
assert_equal "$got" "$want"
}

@test "autosdk :: Run span :: link :: attributes" {
got=$(span_links ${SCOPE} "Run" | jq ".attributes[] | select(.key == \"data\").value.stringValue")
assert_equal "$got" '"Hello World"'
}
7 changes: 7 additions & 0 deletions internal/test/test_helpers/utilities.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ resource_attributes_received() {
spans_received | jq ".resource.attributes[]?"
}

# Returns an array of all span links emitted by a given library/scope and span.
# $1 - library/scope name
# $2 - span name
span_links() {
spans_from_scope_named $1 | jq "select(.name == \"$2\").links[]"
}

# Returns an array of all spans emitted by a given library/scope
# $1 - library/scope name
spans_from_scope_named() {
Expand Down
20 changes: 17 additions & 3 deletions sdk/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,8 @@ func (t tracer) traces(ctx context.Context, name string, cfg trace.SpanConfig, s
start = pcommon.NewTimestampFromTime(time.Now())
}
span.SetStartTimestamp(start)

addLinks(span.Links(), cfg.Links()...)
setAttributes(span.Attributes(), cfg.Attributes())
// TODO: Add Links.

return traces, span
}
Expand Down Expand Up @@ -279,7 +278,22 @@ func (s *span) AddLink(link trace.Link) {
if s == nil || !s.sampled {
return
}
/* TODO: implement */

// TODO: handle link limits.

addLinks(s.span.Links(), link)
}

func addLinks(dest ptrace.SpanLinkSlice, links ...trace.Link) {
dest.EnsureCapacity(len(links))
for _, link := range links {
l := dest.AppendEmpty()
l.SetTraceID(pcommon.TraceID(link.SpanContext.TraceID()))
l.SetSpanID(pcommon.SpanID(link.SpanContext.SpanID()))
l.SetFlags(uint32(link.SpanContext.TraceFlags()))
l.TraceState().FromRaw(link.SpanContext.TraceState().String())
setAttributes(l.Attributes(), link.Attributes)
}
}

func (s *span) SetName(name string) {
Expand Down
60 changes: 60 additions & 0 deletions sdk/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ var (
SpanID: trace.SpanID{0x1},
TraceFlags: trace.FlagsSampled,
})
spanContext1 = trace.NewSpanContext(trace.SpanContextConfig{
TraceID: trace.TraceID{0x2},
SpanID: trace.SpanID{0x2},
TraceFlags: trace.FlagsSampled,
})

link0 = trace.Link{
SpanContext: spanContext0,
Attributes: []attribute.KeyValue{
attribute.Int("n", 0),
},
}
link1 = trace.Link{
SpanContext: spanContext1,
Attributes: []attribute.KeyValue{
attribute.Int("n", 1),
},
}

pLink0 = func() ptrace.SpanLink {
l := ptrace.NewSpanLink()
l.SetTraceID(pcommon.TraceID(spanContext0.TraceID()))
l.SetSpanID(pcommon.SpanID(spanContext0.SpanID()))
l.SetFlags(uint32(spanContext0.TraceFlags()))
l.Attributes().PutInt("n", 0)
return l
}()
pLink1 = func() ptrace.SpanLink {
l := ptrace.NewSpanLink()
l.SetTraceID(pcommon.TraceID(spanContext1.TraceID()))
l.SetSpanID(pcommon.SpanID(spanContext1.SpanID()))
l.SetFlags(uint32(spanContext1.TraceFlags()))
l.Attributes().PutInt("n", 1)
return l
}()
)

func TestSpanCreation(t *testing.T) {
Expand Down Expand Up @@ -207,6 +242,19 @@ func TestSpanCreation(t *testing.T) {
assert.Equal(t, pAttrs, s.span.Attributes())
},
},
{
TestName: "WithLinks",
Options: []trace.SpanStartOption{
trace.WithLinks(link0, link1),
},
Eval: func(t *testing.T, _ context.Context, s *span) {
assertTracer(s.traces)
want := ptrace.NewSpanLinkSlice()
pLink0.CopyTo(want.AppendEmpty())
pLink1.CopyTo(want.AppendEmpty())
assert.Equal(t, want, s.span.Links())
},
},
}

ctx := context.Background()
Expand Down Expand Up @@ -321,6 +369,18 @@ func TestSpanNilUnsampledGuards(t *testing.T) {
t.Run("TracerProvider", run(func(s *span) { _ = s.TracerProvider() }))
}

func TestSpanAddLink(t *testing.T) {
s := spanBuilder{
Options: []trace.SpanStartOption{trace.WithLinks(link0)},
}.Build()
s.AddLink(link1)

want := ptrace.NewSpanLinkSlice()
pLink0.CopyTo(want.AppendEmpty())
pLink1.CopyTo(want.AppendEmpty())
assert.Equal(t, want, s.span.Links())
}

func TestSpanIsRecording(t *testing.T) {
builder := spanBuilder{}
s := builder.Build()
Expand Down
Loading