Skip to content

Commit 3ebb83f

Browse files
datastreams: Port data-streams-go to dd-trace-go (DataDog#2006)
Co-authored-by: Andrew Glaude <andrew.glaude@datadoghq.com>
1 parent b49b27c commit 3ebb83f

35 files changed

+3008
-81
lines changed

CODEOWNERS

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
/contrib/**/*appsec*.go @DataDog/asm-go
1818
/.github/workflows/appsec.yml @DataDog/asm-go
1919

20+
# datastreams
21+
/datastreams @Datadog/data-streams-monitoring
22+
/internal/datastreams @Datadog/data-streams-monitoring
23+
2024
# telemetry
2125
/internal/telemetry @DataDog/apm-go
2226

contrib/Shopify/sarama/option.go

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type config struct {
2020
consumerSpanName string
2121
producerSpanName string
2222
analyticsRate float64
23+
dataStreamsEnabled bool
24+
groupID string
2325
}
2426

2527
func defaults(cfg *config) {
@@ -51,6 +53,20 @@ func WithServiceName(name string) Option {
5153
}
5254
}
5355

56+
// WithDataStreams enables the Data Streams monitoring product features: https://www.datadoghq.com/product/data-streams-monitoring/
57+
func WithDataStreams() Option {
58+
return func(cfg *config) {
59+
cfg.dataStreamsEnabled = true
60+
}
61+
}
62+
63+
// WithGroupID tags the produced data streams metrics with the given groupID (aka consumer group)
64+
func WithGroupID(groupID string) Option {
65+
return func(cfg *config) {
66+
cfg.groupID = groupID
67+
}
68+
}
69+
5470
// WithAnalytics enables Trace Analytics for all started spans.
5571
func WithAnalytics(on bool) Option {
5672
return func(cfg *config) {

contrib/Shopify/sarama/sarama.go

+74
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
package sarama // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/Shopify/sarama"
88

99
import (
10+
"context"
1011
"math"
1112

13+
"gopkg.in/DataDog/dd-trace-go.v1/datastreams"
14+
"gopkg.in/DataDog/dd-trace-go.v1/datastreams/options"
1215
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
1316
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
1417
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
@@ -76,6 +79,7 @@ func WrapPartitionConsumer(pc sarama.PartitionConsumer, opts ...Option) sarama.P
7679
next := tracer.StartSpan(cfg.consumerSpanName, opts...)
7780
// reinject the span context so consumers can pick it up
7881
tracer.Inject(next.Context(), carrier)
82+
setConsumeCheckpoint(cfg.dataStreamsEnabled, cfg.groupID, msg)
7983

8084
wrapped.messages <- msg
8185

@@ -127,8 +131,12 @@ type syncProducer struct {
127131
// SendMessage calls sarama.SyncProducer.SendMessage and traces the request.
128132
func (p *syncProducer) SendMessage(msg *sarama.ProducerMessage) (partition int32, offset int64, err error) {
129133
span := startProducerSpan(p.cfg, p.version, msg)
134+
setProduceCheckpoint(p.cfg.dataStreamsEnabled, msg, p.version)
130135
partition, offset, err = p.SyncProducer.SendMessage(msg)
131136
finishProducerSpan(span, partition, offset, err)
137+
if err == nil && p.cfg.dataStreamsEnabled {
138+
tracer.TrackKafkaProduceOffset(msg.Topic, partition, offset)
139+
}
132140
return partition, offset, err
133141
}
134142

@@ -138,12 +146,19 @@ func (p *syncProducer) SendMessages(msgs []*sarama.ProducerMessage) error {
138146
// treated individually, so we create a span for each one
139147
spans := make([]ddtrace.Span, len(msgs))
140148
for i, msg := range msgs {
149+
setProduceCheckpoint(p.cfg.dataStreamsEnabled, msg, p.version)
141150
spans[i] = startProducerSpan(p.cfg, p.version, msg)
142151
}
143152
err := p.SyncProducer.SendMessages(msgs)
144153
for i, span := range spans {
145154
finishProducerSpan(span, msgs[i].Partition, msgs[i].Offset, err)
146155
}
156+
if err == nil && p.cfg.dataStreamsEnabled {
157+
// we only track Kafka lag if messages have been sent successfully. Otherwise, we have no way to know to which partition data was sent to.
158+
for _, msg := range msgs {
159+
tracer.TrackKafkaProduceOffset(msg.Topic, msg.Partition, msg.Offset)
160+
}
161+
}
147162
return err
148163
}
149164

@@ -221,6 +236,7 @@ func WrapAsyncProducer(saramaConfig *sarama.Config, p sarama.AsyncProducer, opts
221236
select {
222237
case msg := <-wrapped.input:
223238
span := startProducerSpan(cfg, saramaConfig.Version, msg)
239+
setProduceCheckpoint(cfg.dataStreamsEnabled, msg, saramaConfig.Version)
224240
p.Input() <- msg
225241
if saramaConfig.Producer.Return.Successes {
226242
spanID := span.Context().SpanID()
@@ -236,6 +252,10 @@ func WrapAsyncProducer(saramaConfig *sarama.Config, p sarama.AsyncProducer, opts
236252
// producer was closed, so exit
237253
return
238254
}
255+
if cfg.dataStreamsEnabled {
256+
// we only track Kafka lag if returning successes is enabled. Otherwise, we have no way to know to which partition data was sent to.
257+
tracer.TrackKafkaProduceOffset(msg.Topic, msg.Partition, msg.Offset)
258+
}
239259
if spanctx, spanFound := getSpanContext(msg); spanFound {
240260
spanID := spanctx.SpanID()
241261
if span, ok := spans[spanID]; ok {
@@ -303,3 +323,57 @@ func getSpanContext(msg *sarama.ProducerMessage) (ddtrace.SpanContext, bool) {
303323

304324
return spanctx, true
305325
}
326+
327+
func setProduceCheckpoint(enabled bool, msg *sarama.ProducerMessage, version sarama.KafkaVersion) {
328+
if !enabled || msg == nil {
329+
return
330+
}
331+
edges := []string{"direction:out", "topic:" + msg.Topic, "type:kafka"}
332+
carrier := NewProducerMessageCarrier(msg)
333+
ctx, ok := tracer.SetDataStreamsCheckpointWithParams(datastreams.ExtractFromBase64Carrier(context.Background(), carrier), options.CheckpointParams{PayloadSize: getProducerMsgSize(msg)}, edges...)
334+
if !ok || !version.IsAtLeast(sarama.V0_11_0_0) {
335+
return
336+
}
337+
datastreams.InjectToBase64Carrier(ctx, carrier)
338+
}
339+
340+
func setConsumeCheckpoint(enabled bool, groupID string, msg *sarama.ConsumerMessage) {
341+
if !enabled || msg == nil {
342+
return
343+
}
344+
edges := []string{"direction:in", "topic:" + msg.Topic, "type:kafka"}
345+
if groupID != "" {
346+
edges = append(edges, "group:"+groupID)
347+
}
348+
carrier := NewConsumerMessageCarrier(msg)
349+
ctx, ok := tracer.SetDataStreamsCheckpointWithParams(datastreams.ExtractFromBase64Carrier(context.Background(), carrier), options.CheckpointParams{PayloadSize: getConsumerMsgSize(msg)}, edges...)
350+
if !ok {
351+
return
352+
}
353+
datastreams.InjectToBase64Carrier(ctx, carrier)
354+
if groupID != "" {
355+
// only track Kafka lag if a consumer group is set.
356+
// since there is no ack mechanism, we consider that messages read are committed right away.
357+
tracer.TrackKafkaCommitOffset(groupID, msg.Topic, msg.Partition, msg.Offset)
358+
}
359+
}
360+
361+
func getProducerMsgSize(msg *sarama.ProducerMessage) (size int64) {
362+
for _, header := range msg.Headers {
363+
size += int64(len(header.Key) + len(header.Value))
364+
}
365+
if msg.Value != nil {
366+
size += int64(msg.Value.Length())
367+
}
368+
if msg.Key != nil {
369+
size += int64(msg.Key.Length())
370+
}
371+
return size
372+
}
373+
374+
func getConsumerMsgSize(msg *sarama.ConsumerMessage) (size int64) {
375+
for _, header := range msg.Headers {
376+
size += int64(len(header.Key) + len(header.Value))
377+
}
378+
return size + int64(len(msg.Value)+len(msg.Key))
379+
}

contrib/Shopify/sarama/sarama_test.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
"gopkg.in/DataDog/dd-trace-go.v1/contrib/internal/namingschematest"
14+
"gopkg.in/DataDog/dd-trace-go.v1/datastreams"
1415
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
1516
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
1617
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
@@ -115,7 +116,7 @@ func TestConsumer(t *testing.T) {
115116
}
116117
defer consumer.Close()
117118

118-
consumer = WrapConsumer(consumer)
119+
consumer = WrapConsumer(consumer, WithDataStreams())
119120

120121
partitionConsumer, err := consumer.ConsumePartition("test-topic", 0, 0)
121122
if err != nil {
@@ -145,6 +146,12 @@ func TestConsumer(t *testing.T) {
145146
assert.Equal(t, "Shopify/sarama", s.Tag(ext.Component))
146147
assert.Equal(t, ext.SpanKindConsumer, s.Tag(ext.SpanKind))
147148
assert.Equal(t, "kafka", s.Tag(ext.MessagingSystem))
149+
p, ok := datastreams.PathwayFromContext(datastreams.ExtractFromBase64Carrier(context.Background(), NewConsumerMessageCarrier(msg1)))
150+
assert.True(t, ok)
151+
expectedCtx, _ := tracer.SetDataStreamsCheckpoint(context.Background(), "direction:in", "topic:test-topic", "type:kafka")
152+
expected, _ := datastreams.PathwayFromContext(expectedCtx)
153+
assert.NotEqual(t, expected.GetHash(), 0)
154+
assert.Equal(t, expected.GetHash(), p.GetHash())
148155
}
149156
{
150157
s := spans[1]
@@ -162,6 +169,12 @@ func TestConsumer(t *testing.T) {
162169
assert.Equal(t, "Shopify/sarama", s.Tag(ext.Component))
163170
assert.Equal(t, ext.SpanKindConsumer, s.Tag(ext.SpanKind))
164171
assert.Equal(t, "kafka", s.Tag(ext.MessagingSystem))
172+
p, ok := datastreams.PathwayFromContext(datastreams.ExtractFromBase64Carrier(context.Background(), NewConsumerMessageCarrier(msg1)))
173+
assert.True(t, ok)
174+
expectedCtx, _ := tracer.SetDataStreamsCheckpoint(context.Background(), "direction:in", "topic:test-topic", "type:kafka")
175+
expected, _ := datastreams.PathwayFromContext(expectedCtx)
176+
assert.NotEqual(t, expected.GetHash(), 0)
177+
assert.Equal(t, expected.GetHash(), p.GetHash())
165178
}
166179
}
167180

@@ -176,23 +189,25 @@ func TestSyncProducer(t *testing.T) {
176189
defer leader.Close()
177190

178191
metadataResponse := new(sarama.MetadataResponse)
192+
metadataResponse.Version = 1
179193
metadataResponse.AddBroker(leader.Addr(), leader.BrokerID())
180194
metadataResponse.AddTopicPartition("my_topic", 0, leader.BrokerID(), nil, nil, nil, sarama.ErrNoError)
181195
seedBroker.Returns(metadataResponse)
182196

183197
prodSuccess := new(sarama.ProduceResponse)
198+
prodSuccess.Version = 2
184199
prodSuccess.AddTopicPartition("my_topic", 0, sarama.ErrNoError)
185200
leader.Returns(prodSuccess)
186201

187202
cfg := sarama.NewConfig()
188-
cfg.Version = sarama.MinVersion
203+
cfg.Version = sarama.V0_11_0_0 // first version that supports headers
189204
cfg.Producer.Return.Successes = true
190205

191206
producer, err := sarama.NewSyncProducer([]string{seedBroker.Addr()}, cfg)
192207
if err != nil {
193208
t.Fatal(err)
194209
}
195-
producer = WrapSyncProducer(cfg, producer)
210+
producer = WrapSyncProducer(cfg, producer, WithDataStreams())
196211

197212
msg1 := &sarama.ProducerMessage{
198213
Topic: "my_topic",
@@ -214,6 +229,12 @@ func TestSyncProducer(t *testing.T) {
214229
assert.Equal(t, "Shopify/sarama", s.Tag(ext.Component))
215230
assert.Equal(t, ext.SpanKindProducer, s.Tag(ext.SpanKind))
216231
assert.Equal(t, "kafka", s.Tag(ext.MessagingSystem))
232+
p, ok := datastreams.PathwayFromContext(datastreams.ExtractFromBase64Carrier(context.Background(), NewProducerMessageCarrier(msg1)))
233+
assert.True(t, ok)
234+
expectedCtx, _ := tracer.SetDataStreamsCheckpoint(context.Background(), "direction:out", "topic:my_topic", "type:kafka")
235+
expected, _ := datastreams.PathwayFromContext(expectedCtx)
236+
assert.NotEqual(t, expected.GetHash(), 0)
237+
assert.Equal(t, expected.GetHash(), p.GetHash())
217238
}
218239
}
219240

contrib/confluentinc/confluent-kafka-go/kafka.v2/example_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var (
2121

2222
// This example shows how a span context can be passed from a producer to a consumer.
2323
func Example() {
24+
2425
tracer.Start()
2526
defer tracer.Stop()
2627

@@ -31,6 +32,7 @@ func Example() {
3132
"session.timeout.ms": 10,
3233
"enable.auto.offset.store": false,
3334
})
35+
3436
err = c.Subscribe(testTopic, nil)
3537
if err != nil {
3638
panic(err)
@@ -56,6 +58,7 @@ func Example() {
5658
tracer.Inject(parentSpan.Context(), carrier)
5759

5860
c.Consumer.Events() <- msg
61+
5962
}()
6063

6164
msg := (<-c.Events()).(*kafka.Message)
@@ -66,6 +69,7 @@ func Example() {
6669
if err != nil {
6770
panic(err)
6871
}
72+
6973
parentContext := parentSpan.Context()
7074

7175
// Validate that the context passed is the context sent via the message

0 commit comments

Comments
 (0)