Skip to content

Commit 080bac9

Browse files
feat: support container tags hash in DSM (DataDog#4759)
This is a missing feature in dd-trace-go, here are the other implementations ([java](DataDog/dd-trace-java#9156), [node](DataDog/dd-trace-js#7212), [python](DataDog/dd-trace-py#15293)) This PR stores the has propagated back by the info endpoint (called every 5s) and enrich it in DSM hash computation ### Motivation <!-- * What inspired you to submit this pull request? * Link any related GitHub issues or PRs here. * If this resolves a GitHub issue, include "Fixes #XXXX" to link the issue and auto-close it on merge. --> ### Reviewer's Checklist <!-- * Authors can use this list as a reference to ensure that there are no problems during the review but the signing off is to be done by the reviewer(s). --> - [ ] Changed code has unit tests for its functionality at or near 100% coverage. - [ ] [System-Tests](https://github.com/DataDog/system-tests/) covering this feature have been added and enabled with the va.b.c-dev version tag. - [ ] There is a benchmark for any new code, or changes to existing code. - [ ] If this interacts with the agent in a new way, a system test has been added. - [ ] New code is free of linting errors. You can check this by running `make lint` locally. - [ ] New code doesn't break existing tests. You can check this by running `make test` locally. - [ ] Add an appropriate team label so this PR gets put in the right place for the release notes. - [ ] All generated files are up to date. You can check this by running `make generate` locally. - [ ] Non-trivial go.mod changes, e.g. adding new modules, are reviewed by @DataDog/dd-trace-go-guild. Make sure all nested modules are up to date by running `make fix-modules` locally. Unsure? Have a question? Request a review! Co-authored-by: raphael.gavache <raphael.gavache@datadoghq.com>
1 parent fff4209 commit 080bac9

11 files changed

Lines changed: 191 additions & 44 deletions

File tree

ddtrace/tracer/option.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ func fetchAgentFeatures(ctx context.Context, agentURL *url.URL, httpClient *http
691691
if err != nil {
692692
return agentFeatures{}, fmt.Errorf("creating /info request: %w", err)
693693
}
694+
setContainerHeaders(req.Header)
694695
resp, err := httpClient.Do(req)
695696
if err != nil {
696697
return agentFeatures{}, fmt.Errorf("loading features: %w", err)
@@ -704,6 +705,7 @@ func fetchAgentFeatures(ctx context.Context, agentURL *url.URL, httpClient *http
704705
if resp.StatusCode != http.StatusOK {
705706
return agentFeatures{}, fmt.Errorf("unexpected /info status: %d", resp.StatusCode)
706707
}
708+
updateContainerTagsHash(resp.Header)
707709
type infoResponse struct {
708710
Endpoints []string `json:"endpoints"`
709711
ClientDropP0s bool `json:"client_drop_p0s"`

ddtrace/tracer/transport.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
"github.com/DataDog/dd-trace-go/v2/ddtrace/internal/tracerstats"
2121
"github.com/DataDog/dd-trace-go/v2/internal"
22+
"github.com/DataDog/dd-trace-go/v2/internal/processtags"
2223
"github.com/DataDog/dd-trace-go/v2/internal/version"
2324

2425
"github.com/tinylib/msgp/msgp"
@@ -38,6 +39,9 @@ const (
3839
defaultHTTPTimeout = 10 * time.Second // defines the current timeout before giving up with the send process
3940
traceCountHeader = "X-Datadog-Trace-Count" // header containing the number of traces in the payload
4041
obfuscationVersionHeader = "Datadog-Obfuscation-Version" // header containing the version of obfuscation used, if any
42+
containerIDHeader = "Datadog-Container-ID"
43+
entityIDHeader = "Datadog-Entity-ID"
44+
containerTagsHashHeader = "Datadog-Container-Tags-Hash"
4145

4246
tracesAPIPath = "/v0.4/traces"
4347
tracesAPIPathV1 = "/v1.0/traces"
@@ -87,17 +91,32 @@ func datadogHeaders() map[string]string {
8791
"Content-Type": "application/msgpack",
8892
}
8993
if cid := internal.ContainerID(); cid != "" {
90-
h["Datadog-Container-ID"] = cid
94+
h[containerIDHeader] = cid
9195
}
9296
if eid := internal.EntityID(); eid != "" {
93-
h["Datadog-Entity-ID"] = eid
97+
h[entityIDHeader] = eid
9498
}
9599
if extEnv := internal.ExternalEnvironment(); extEnv != "" {
96100
h["Datadog-External-Env"] = extEnv
97101
}
98102
return h
99103
}
100104

105+
func setContainerHeaders(h http.Header) {
106+
if cid := internal.ContainerID(); cid != "" {
107+
h.Set(containerIDHeader, cid)
108+
}
109+
if eid := internal.EntityID(); eid != "" {
110+
h.Set(entityIDHeader, eid)
111+
}
112+
}
113+
114+
func updateContainerTagsHash(h http.Header) {
115+
if hash := h.Get(containerTagsHashHeader); hash != "" {
116+
processtags.SetContainerTagsHash(hash)
117+
}
118+
}
119+
101120
func (t *httpTransport) sendStats(p *pb.ClientStatsPayload, tracerObfuscationVersion int) error {
102121
var buf bytes.Buffer
103122
if err := msgp.Encode(&buf, p); err != nil {

ddtrace/tracer/transport_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package tracer
77

88
import (
9+
"context"
910
"fmt"
1011
"io"
1112
"net"
@@ -26,6 +27,7 @@ import (
2627
tinternal "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer/internal"
2728
"github.com/DataDog/dd-trace-go/v2/internal"
2829
internalconfig "github.com/DataDog/dd-trace-go/v2/internal/config"
30+
"github.com/DataDog/dd-trace-go/v2/internal/processtags"
2931
"github.com/DataDog/dd-trace-go/v2/internal/statsdtest"
3032

3133
"github.com/stretchr/testify/assert"
@@ -183,6 +185,26 @@ func TestTransportResponse(t *testing.T) {
183185
}
184186
}
185187

188+
func TestFetchAgentFeaturesContainerTagsHash(t *testing.T) {
189+
t.Cleanup(func() { processtags.SetContainerTagsHash("") })
190+
processtags.SetContainerTagsHash("")
191+
192+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
193+
require.Equal(t, "/info", r.URL.Path)
194+
w.Header().Set(containerTagsHashHeader, "info-container-hash")
195+
w.Write([]byte(`{"endpoints":["/v0.6/stats"],"client_drop_p0s":true,"config":{}}`))
196+
}))
197+
defer srv.Close()
198+
199+
agentURL, err := url.Parse(srv.URL)
200+
require.NoError(t, err)
201+
features, err := fetchAgentFeatures(context.Background(), agentURL, srv.Client())
202+
require.NoError(t, err)
203+
204+
assert.True(t, features.Stats)
205+
assert.Equal(t, "info-container-hash", processtags.ContainerTagsHash())
206+
}
207+
186208
func TestTraceCountHeader(t *testing.T) {
187209
assert := assert.New(t)
188210

internal/datastreams/hash_cache.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type hashCache struct {
1919
m map[string]uint64
2020
}
2121

22-
func getHashKey(edgeTags, processTags []string, parentHash uint64) string {
22+
func getHashKey(edgeTags, processTags []string, containerTagsHash string, parentHash uint64) string {
2323
var s strings.Builder
2424
l := 0
2525
for _, t := range edgeTags {
@@ -28,6 +28,7 @@ func getHashKey(edgeTags, processTags []string, parentHash uint64) string {
2828
for _, t := range processTags {
2929
l += len(t)
3030
}
31+
l += len(containerTagsHash)
3132
l += 8
3233
s.Grow(l)
3334
for _, t := range edgeTags {
@@ -36,6 +37,7 @@ func getHashKey(edgeTags, processTags []string, parentHash uint64) string {
3637
for _, t := range processTags {
3738
s.WriteString(t)
3839
}
40+
s.WriteString(containerTagsHash)
3941
s.WriteByte(byte(parentHash))
4042
s.WriteByte(byte(parentHash >> 8))
4143
s.WriteByte(byte(parentHash >> 16))
@@ -47,8 +49,8 @@ func getHashKey(edgeTags, processTags []string, parentHash uint64) string {
4749
return s.String()
4850
}
4951

50-
func (c *hashCache) computeAndGet(key string, parentHash uint64, service, env string, edgeTags, processTags []string) uint64 {
51-
hash := pathwayHash(nodeHash(service, env, edgeTags, processTags), parentHash)
52+
func (c *hashCache) computeAndGet(key string, parentHash uint64, service, env string, edgeTags, processTags []string, containerTagsHash string) uint64 {
53+
hash := pathwayHash(nodeHash(service, env, edgeTags, processTags, containerTagsHash), parentHash)
5254
c.mu.Lock()
5355
defer c.mu.Unlock()
5456
if len(c.m) >= maxHashCacheSize {
@@ -60,15 +62,15 @@ func (c *hashCache) computeAndGet(key string, parentHash uint64, service, env st
6062
return hash
6163
}
6264

63-
func (c *hashCache) get(service, env string, edgeTags, processTags []string, parentHash uint64) uint64 {
64-
key := getHashKey(edgeTags, processTags, parentHash)
65+
func (c *hashCache) get(service, env string, edgeTags, processTags []string, containerTagsHash string, parentHash uint64) uint64 {
66+
key := getHashKey(edgeTags, processTags, containerTagsHash, parentHash)
6567
c.mu.RLock()
6668
if hash, ok := c.m[key]; ok {
6769
c.mu.RUnlock()
6870
return hash
6971
}
7072
c.mu.RUnlock()
71-
return c.computeAndGet(key, parentHash, service, env, edgeTags, processTags)
73+
return c.computeAndGet(key, parentHash, service, env, edgeTags, processTags, containerTagsHash)
7274
}
7375

7476
func newHashCache() *hashCache {

internal/datastreams/hash_cache_test.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,29 @@ import (
1414

1515
func TestHashCache(t *testing.T) {
1616
cache := newHashCache()
17-
assert.Equal(t, pathwayHash(nodeHash("service", "env", []string{"type:kafka"}, nil), 1234), cache.get("service", "env", []string{"type:kafka"}, nil, 1234))
17+
assert.Equal(t, pathwayHash(nodeHash("service", "env", []string{"type:kafka"}, nil, ""), 1234), cache.get("service", "env", []string{"type:kafka"}, nil, "", 1234))
1818
assert.Len(t, cache.m, 1)
19-
assert.Equal(t, pathwayHash(nodeHash("service", "env", []string{"type:kafka"}, nil), 1234), cache.get("service", "env", []string{"type:kafka"}, nil, 1234))
19+
assert.Equal(t, pathwayHash(nodeHash("service", "env", []string{"type:kafka"}, nil, ""), 1234), cache.get("service", "env", []string{"type:kafka"}, nil, "", 1234))
2020
assert.Len(t, cache.m, 1)
21-
assert.Equal(t, pathwayHash(nodeHash("service", "env", []string{"type:kafka2"}, nil), 1234), cache.get("service", "env", []string{"type:kafka2"}, nil, 1234))
21+
assert.Equal(t, pathwayHash(nodeHash("service", "env", []string{"type:kafka2"}, nil, ""), 1234), cache.get("service", "env", []string{"type:kafka2"}, nil, "", 1234))
2222
assert.Len(t, cache.m, 2)
2323

2424
pTags := []string{"entrypoint.name:something", "entrypoint.type:executable"}
25-
h1 := pathwayHash(nodeHash("service", "env", []string{"type:kafka"}, pTags), 1234)
26-
h2 := cache.get("service", "env", []string{"type:kafka"}, pTags, 1234)
25+
h1 := pathwayHash(nodeHash("service", "env", []string{"type:kafka"}, pTags, "container-hash"), 1234)
26+
h2 := cache.get("service", "env", []string{"type:kafka"}, pTags, "container-hash", 1234)
2727

2828
assert.Equal(t, h1, h2)
2929
assert.Len(t, cache.m, 3)
30+
31+
h3 := cache.get("service", "env", []string{"type:kafka"}, pTags, "other-container-hash", 1234)
32+
assert.NotEqual(t, h2, h3)
33+
assert.Len(t, cache.m, 4)
3034
}
3135

3236
func TestGetHashKey(t *testing.T) {
3337
parentHash := uint64(87234)
34-
key := getHashKey([]string{"type:kafka", "topic:topic1", "group:group1"}, []string{"entrypoint.name:something", "entrypoint.type:executable"}, parentHash)
38+
key := getHashKey([]string{"type:kafka", "topic:topic1", "group:group1"}, []string{"entrypoint.name:something", "entrypoint.type:executable"}, "container-hash", parentHash)
3539
hash := make([]byte, 8)
3640
binary.LittleEndian.PutUint64(hash, parentHash)
37-
assert.Equal(t, "type:kafkatopic:topic1group:group1entrypoint.name:somethingentrypoint.type:executable"+string(hash), key)
41+
assert.Equal(t, "type:kafkatopic:topic1group:group1entrypoint.name:somethingentrypoint.type:executablecontainer-hash"+string(hash), key)
3842
}

internal/datastreams/pathway.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func isWellFormedEdgeTag(t string) bool {
2626
return false
2727
}
2828

29-
func nodeHash(service, env string, edgeTags, processTags []string) uint64 {
29+
func nodeHash(service, env string, edgeTags, processTags []string, containerTagsHash string) uint64 {
3030
h := fnv.New64()
3131
sort.Strings(edgeTags)
3232
h.Write([]byte(service))
@@ -41,6 +41,9 @@ func nodeHash(service, env string, edgeTags, processTags []string) uint64 {
4141
for _, t := range processTags {
4242
h.Write([]byte(t))
4343
}
44+
if containerTagsHash != "" {
45+
h.Write([]byte(containerTagsHash))
46+
}
4447
return h.Sum64()
4548
}
4649

internal/datastreams/pathway_test.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ func TestPathway(t *testing.T) {
3535
end := middle.Add(time.Hour)
3636
processor.timeSource = func() time.Time { return end }
3737
ctx = processor.SetCheckpoint(ctx, "topic:topic2")
38-
hash1 := pathwayHash(nodeHash("service-1", "env", nil, processTags), 0)
39-
hash2 := pathwayHash(nodeHash("service-1", "env", []string{"topic:topic1"}, processTags), hash1)
40-
hash3 := pathwayHash(nodeHash("service-1", "env", []string{"topic:topic2"}, processTags), hash2)
38+
hash1 := pathwayHash(nodeHash("service-1", "env", nil, processTags, ""), 0)
39+
hash2 := pathwayHash(nodeHash("service-1", "env", []string{"topic:topic1"}, processTags, ""), hash1)
40+
hash3 := pathwayHash(nodeHash("service-1", "env", []string{"topic:topic2"}, processTags, ""), hash2)
4141
p, _ := PathwayFromContext(ctx)
4242
assert.Equal(t, hash3, p.GetHash())
4343
assert.Equal(t, start, p.PathwayStart())
@@ -86,9 +86,9 @@ func TestPathway(t *testing.T) {
8686
pathwayWith2EdgeTags, _ := PathwayFromContext(processor.SetCheckpoint(context.Background(), "type:internal", "some_other_key:some_other_val"))
8787

8888
processTags := processtags.GlobalTags().Slice()
89-
hash1 := pathwayHash(nodeHash("service-1", "env", nil, processTags), 0)
90-
hash2 := pathwayHash(nodeHash("service-1", "env", []string{"type:internal"}, processTags), 0)
91-
hash3 := pathwayHash(nodeHash("service-1", "env", []string{"type:internal", "some_other_key:some_other_val"}, processTags), 0)
89+
hash1 := pathwayHash(nodeHash("service-1", "env", nil, processTags, ""), 0)
90+
hash2 := pathwayHash(nodeHash("service-1", "env", []string{"type:internal"}, processTags, ""), 0)
91+
hash3 := pathwayHash(nodeHash("service-1", "env", []string{"type:internal", "some_other_key:some_other_val"}, processTags, ""), 0)
9292
assert.Equal(t, hash1, pathwayWithNoEdgeTags.GetHash())
9393
assert.Equal(t, hash2, pathwayWith1EdgeTag.GetHash())
9494
assert.Equal(t, hash3, pathwayWith2EdgeTags.GetHash())
@@ -106,28 +106,32 @@ func TestPathway(t *testing.T) {
106106

107107
t.Run("test nodeHash", func(t *testing.T) {
108108
assert.NotEqual(t,
109-
nodeHash("service-1", "env", []string{"type:internal"}, nil),
110-
nodeHash("service-1", "env", []string{"type:kafka"}, nil),
109+
nodeHash("service-1", "env", []string{"type:internal"}, nil, ""),
110+
nodeHash("service-1", "env", []string{"type:kafka"}, nil, ""),
111111
)
112112
assert.NotEqual(t,
113-
nodeHash("service-1", "env", []string{"exchange:1"}, nil),
114-
nodeHash("service-1", "env", []string{"exchange:2"}, nil),
113+
nodeHash("service-1", "env", []string{"exchange:1"}, nil, ""),
114+
nodeHash("service-1", "env", []string{"exchange:2"}, nil, ""),
115115
)
116116
assert.NotEqual(t,
117-
nodeHash("service-1", "env", []string{"topic:1"}, nil),
118-
nodeHash("service-1", "env", []string{"topic:2"}, nil),
117+
nodeHash("service-1", "env", []string{"topic:1"}, nil, ""),
118+
nodeHash("service-1", "env", []string{"topic:2"}, nil, ""),
119119
)
120120
assert.NotEqual(t,
121-
nodeHash("service-1", "env", []string{"group:1"}, nil),
122-
nodeHash("service-1", "env", []string{"group:2"}, nil),
121+
nodeHash("service-1", "env", []string{"group:1"}, nil, ""),
122+
nodeHash("service-1", "env", []string{"group:2"}, nil, ""),
123123
)
124124
assert.NotEqual(t,
125-
nodeHash("service-1", "env", []string{"event_type:1"}, nil),
126-
nodeHash("service-1", "env", []string{"event_type:2"}, nil),
125+
nodeHash("service-1", "env", []string{"event_type:1"}, nil, ""),
126+
nodeHash("service-1", "env", []string{"event_type:2"}, nil, ""),
127127
)
128128
assert.Equal(t,
129-
nodeHash("service-1", "env", []string{"partition:0"}, nil),
130-
nodeHash("service-1", "env", []string{"partition:1"}, nil),
129+
nodeHash("service-1", "env", []string{"partition:0"}, nil, ""),
130+
nodeHash("service-1", "env", []string{"partition:1"}, nil, ""),
131+
)
132+
assert.NotEqual(t,
133+
nodeHash("service-1", "env", []string{"type:kafka"}, nil, "container-hash-1"),
134+
nodeHash("service-1", "env", []string{"type:kafka"}, nil, "container-hash-2"),
131135
)
132136
})
133137

@@ -171,7 +175,7 @@ func TestPathway(t *testing.T) {
171175
})
172176

173177
t.Run("test GetHash", func(t *testing.T) {
174-
pathway := Pathway{hash: nodeHash("service", "env", []string{"direction:in"}, nil)}
178+
pathway := Pathway{hash: nodeHash("service", "env", []string{"direction:in"}, nil, "")}
175179
assert.Equal(t, pathway.hash, pathway.GetHash())
176180
})
177181
}
@@ -187,6 +191,6 @@ func BenchmarkNodeHash(b *testing.B) {
187191
env := "test"
188192
edgeTags := []string{"event_type:dog", "exchange:local", "group:all", "topic:off", "type:writer"}
189193
for b.Loop() {
190-
nodeHash(service, env, edgeTags, nil)
194+
nodeHash(service, env, edgeTags, nil, "")
191195
}
192196
}

internal/datastreams/processor.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -641,9 +641,14 @@ func (p *Processor) SetCheckpointWithParams(ctx context.Context, params options.
641641
if params.ServiceOverride != "" {
642642
service = params.ServiceOverride
643643
}
644-
processTags := processtags.GlobalTags().Slice()
644+
var processTags []string
645+
var containerTagsHash string
646+
if pTags := processtags.GlobalTags(); pTags != nil {
647+
processTags = pTags.Slice()
648+
containerTagsHash = processtags.ContainerTagsHash()
649+
}
645650
child := Pathway{
646-
hash: p.hashCache.get(service, p.env, edgeTags, processTags, parentHash),
651+
hash: p.hashCache.get(service, p.env, edgeTags, processTags, containerTagsHash, parentHash),
647652
pathwayStart: pathwayStart,
648653
edgeStart: now,
649654
}

0 commit comments

Comments
 (0)