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

ddtrace/tracer: added support for metrics tag sampling #2662

Merged
merged 4 commits into from
Apr 19, 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
25 changes: 19 additions & 6 deletions ddtrace/tracer/rules_sampler.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,24 @@ func (sr *SamplingRule) match(s *span) bool {
}
s.Lock()
defer s.Unlock()
if sr.Tags != nil && s.Meta != nil {
if sr.Tags != nil {
for k, regex := range sr.Tags {
v, ok := s.Meta[k]
if !ok || !regex.MatchString(v) {
return false
if regex == nil {
continue
}
if s.Meta != nil {
v, ok := s.Meta[k]
if ok && regex.MatchString(v) {
continue
}
}
if s.Metrics != nil {
v, ok := s.Metrics[k]
// sampling on numbers with floating point is not supported,
// thus 'math.Floor(v) != v'
if !ok || math.Floor(v) != v || !regex.MatchString(strconv.FormatFloat(v, 'g', -1, 64)) {
return false
}
}
}
}
Expand Down Expand Up @@ -673,7 +686,7 @@ func newSingleSpanRateLimiter(mps float64) *rateLimiter {
// globMatch compiles pattern string into glob format, i.e. regular expressions with only '?'
// and '*' treated as regex metacharacters.
func globMatch(pattern string) *regexp.Regexp {
if pattern == "" {
if pattern == "" || pattern == "*" {
return nil
}
// escaping regex characters
Expand All @@ -682,7 +695,7 @@ func globMatch(pattern string) *regexp.Regexp {
pattern = strings.Replace(pattern, "\\?", ".", -1)
pattern = strings.Replace(pattern, "\\*", ".*", -1)
// pattern must match an entire string
return regexp.MustCompile(fmt.Sprintf("^%s$", pattern))
return regexp.MustCompile(fmt.Sprintf("(?i)^%s$", pattern))
}

// samplingRulesFromEnv parses sampling rules from
Expand Down
125 changes: 91 additions & 34 deletions ddtrace/tracer/sampler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ func TestRuleEnvVars(t *testing.T) {
{
rules: `[{"name": "abcd?", "sample_rate": 1.0}]`,
srvRegex: "",
nameRegex: "^abcd.$",
nameRegex: "(?i)^abcd.$",
rate: 1.0,
},
{
Expand All @@ -381,34 +381,34 @@ func TestRuleEnvVars(t *testing.T) {
{
rules: `[{"name": "abcd?"}]`,
srvRegex: "",
nameRegex: "^abcd.$",
nameRegex: "(?i)^abcd.$",
rate: 1.0,
},
{
rules: `[{"service": "*abcd", "sample_rate":0.5}]`,
nameRegex: "",
srvRegex: "^.*abcd$",
srvRegex: "(?i)^.*abcd$",
rate: 0.5,
},
{
rules: `[{"service": "*abcd", "sample_rate": 0.5}]`,
nameRegex: "",
srvRegex: "^.*abcd$",
srvRegex: "(?i)^.*abcd$",
rate: 0.5,
},
{
rules: `[{"service": "*abcd", "sample_rate": 0.5,"resource": "root", "tags": {"host":"h-1234*"}}]`,
resourceRegex: "^root$",
tagsRegex: map[string]string{"host": "^h-1234.*$"},
resourceRegex: "(?i)^root$",
tagsRegex: map[string]string{"host": "(?i)^h-1234.*$"},
nameRegex: "",
srvRegex: "^.*abcd$",
srvRegex: "(?i)^.*abcd$",
rate: 0.5,
},
{
rules: `[{"service": "*abcd", "sample_rate": 0.5,"resource": "rsc-[0-9]+" }]`,
resourceRegex: "^rsc-\\[0-9\\]\\+$",
resourceRegex: "(?i)^rsc-\\[0-9\\]\\+$",
nameRegex: "",
srvRegex: "^.*abcd$",
srvRegex: "(?i)^.*abcd$",
rate: 0.5,
},
} {
Expand Down Expand Up @@ -446,10 +446,10 @@ func TestRulesSampler(t *testing.T) {
s.setMeta("hostname", "hn-30")
return s
}
makeFinishedSpan := func(op, svc, resource string, tags map[string]string) *span {
makeFinishedSpan := func(op, svc, resource string, tags map[string]interface{}) *span {
s := newSpan(op, svc, resource, random.Uint64(), random.Uint64(), 0)
for k, v := range tags {
s.setMeta(k, v)
s.SetTag(k, v)
}
s.finished = true
return s
Expand All @@ -469,7 +469,7 @@ func TestRulesSampler(t *testing.T) {
spanSrv string
spanName string
spanRsc string
spanTags map[string]string
spanTags map[string]interface{}
}{
{
rules: `[{"service": "web.non-matching*", "sample_rate": 0}, {"service": "web*", "sample_rate": 1}]`,
Expand Down Expand Up @@ -502,17 +502,17 @@ func TestRulesSampler(t *testing.T) {
rules: `[{"resource": "http_*", "tags":{"host":"COMP-*"}, "sample_rate": 1}]`,
spanSrv: "web.service",
spanRsc: "http_rec",
spanTags: map[string]string{"host": "COMP-1234"},
spanTags: map[string]interface{}{"host": "COMP-1234"},
},
{
rules: `[{"tags":{"host":"COMP-*"}, "sample_rate": 1}]`,
spanSrv: "web.service",
spanTags: map[string]string{"host": "COMP-1234"},
spanTags: map[string]interface{}{"host": "COMP-1234"},
},
{
rules: `[{"tags":{"host":"COMP-*"}, "sample_rate": 1}]`,
spanSrv: "web.service",
spanTags: map[string]string{"host": "COMP-1234"},
spanTags: map[string]interface{}{"host": "COMP-1234"},
},
} {
t.Run("", func(t *testing.T) {
Expand Down Expand Up @@ -619,7 +619,7 @@ func TestRulesSampler(t *testing.T) {
assert := assert.New(t)
rs := newRulesSampler(nil, rules, globalSampleRate())

span := makeFinishedSpan(tt.spanName, tt.spanSrv, "res-10", map[string]string{"hostname": "hn-30"})
span := makeFinishedSpan(tt.spanName, tt.spanSrv, "res-10", map[string]interface{}{"hostname": "hn-30"})

result := rs.SampleSpan(span)
assert.True(result)
Expand All @@ -631,7 +631,7 @@ func TestRulesSampler(t *testing.T) {
})

t.Run("matching-span-rules", func(t *testing.T) {
for _, tt := range []struct {
for i, tt := range []struct {
rules []SamplingRule
spanSrv string
spanName string
Expand Down Expand Up @@ -706,13 +706,47 @@ func TestRulesSampler(t *testing.T) {
spanSrv: "test-service",
spanName: "abcde",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"hostname": "hn*", "tier": "20?"}, "", "abc*", "test*", 1.0)},
spanSrv: "test-service",
spanName: "abcde",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"hostname": "hn*", "tier": "2*"}, "", "abc*", "test*", 1.0)},
spanSrv: "test-service",
spanName: "abcde",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"hostname": "hn*", "tier": "*"}, "", "abc*", "test*", 1.0)},
spanSrv: "test-service",
spanName: "abcde",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"hostname": "hn*", "tag": "*"}, "", "abc*", "test*", 1.0)},
spanSrv: "test-service",
spanName: "abcde",
},
{
rules: []SamplingRule{SpanNameServiceRule("", "web*", 1.0)},
spanSrv: "wEbServer",
spanName: "web.reqUEst",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"shall-pass": "true"}, "", "abc*", "test*", 1.0)},
spanSrv: "test-service",
spanName: "abcde",
},
} {
t.Run("", func(t *testing.T) {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
assert := assert.New(t)
c := newConfig(WithSamplingRules(tt.rules))
rs := newRulesSampler(nil, c.spanRules, globalSampleRate())

span := makeFinishedSpan(tt.spanName, tt.spanSrv, "res-10", map[string]string{"hostname": "hn-30"})
span := makeFinishedSpan(tt.spanName, tt.spanSrv, "res-10", map[string]interface{}{"hostname": "hn-30",
"tag": 20.1,
"tier": 209,
"shall-pass": true,
})
result := rs.SampleSpan(span)
assert.True(result)
assert.Contains(span.Metrics, keySpanSamplingMechanism)
Expand Down Expand Up @@ -776,7 +810,7 @@ func TestRulesSampler(t *testing.T) {
assert := assert.New(t)
rs := newRulesSampler(nil, rules, globalSampleRate())

span := makeFinishedSpan(tt.spanName, tt.spanSrv, tt.resName, map[string]string{"hostname": "hn-30"})
span := makeFinishedSpan(tt.spanName, tt.spanSrv, tt.resName, map[string]interface{}{"hostname": "hn-30"})
result := rs.SampleSpan(span)
assert.False(result)
assert.NotContains(span.Metrics, keySpanSamplingMechanism)
Expand Down Expand Up @@ -859,13 +893,36 @@ func TestRulesSampler(t *testing.T) {
spanSrv: "test-service",
spanName: "abcde",
},

{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"tag": "20"}, "", "", "", 1.0)},
spanSrv: "wEbServer",
spanName: "web.reqUEst",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"tag": "2*"}, "", "", "", 1.0)},
spanSrv: "wEbServer",
spanName: "web.reqUEst",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"tag": "2?"}, "", "", "", 1.0)},
spanSrv: "wEbServer",
spanName: "web.reqUEst",
},
{
rules: []SamplingRule{SpanTagsResourceRule(map[string]string{"hostname": "hn*", "tag": "2*"}, "", "abc*", "test*", 1.0)},
spanSrv: "test-service",
spanName: "abcde",
},
} {
t.Run("", func(t *testing.T) {
assert := assert.New(t)
c := newConfig(WithSamplingRules(tt.rules))
rs := newRulesSampler(nil, c.spanRules, globalSampleRate())

span := makeFinishedSpan(tt.spanName, tt.spanSrv, "res-10", map[string]string{"hostname": "hn-30"})
span := makeFinishedSpan(tt.spanName, tt.spanSrv, "res-10", map[string]interface{}{"hostname": "hn-30",
"tag": 20.1,
})
result := rs.SampleSpan(span)
assert.False(result)
assert.NotContains(span.Metrics, keySpanSamplingMechanism)
Expand Down Expand Up @@ -1425,22 +1482,22 @@ func TestSamplingRuleMarshallGlob(t *testing.T) {
marshal string
}{
// pattern with *
{"test*", "test", regexp.MustCompile("^test.*$"), `{"service":"test*","sample_rate":1,"type":"1"}`},
{"*test", "a-test", regexp.MustCompile("^.*test$"), `{"service":"*test","sample_rate":1,"type":"1"}`},
{"a*case", "acase", regexp.MustCompile("^a.*case$"), `{"service":"a*case","sample_rate":1,"type":"1"}`},
{"test*", "test", regexp.MustCompile("(?i)^test.*$"), `{"service":"test*","sample_rate":1,"type":"1"}`},
{"*test", "a-test", regexp.MustCompile("(?i)^.*test$"), `{"service":"*test","sample_rate":1,"type":"1"}`},
{"a*case", "acase", regexp.MustCompile("(?i)^a.*case$"), `{"service":"a*case","sample_rate":1,"type":"1"}`},
// pattern regexp.MustCompile(), ``, with ?
{"a?case", "a-case", regexp.MustCompile("^a.case$"), `{"service":"a?case","sample_rate":1,"type":"1"}`},
{"a?test?case", "a-test-case", regexp.MustCompile("^a.test.case$"), `{"service":"a?test?case","sample_rate":1,"type":"1"}`},
{"a?case", "a-case", regexp.MustCompile("(?i)^a.case$"), `{"service":"a?case","sample_rate":1,"type":"1"}`},
{"a?test?case", "a-test-case", regexp.MustCompile("(?i)^a.test.case$"), `{"service":"a?test?case","sample_rate":1,"type":"1"}`},
//// pattern with ? regexp.MustCompile(), ``, and *
{"?test*", "atest", regexp.MustCompile("^.test.*$"), `{"service":"?test*","sample_rate":1,"type":"1"}`},
{"test*case", "testcase", regexp.MustCompile("^test.*case$"), `{"service":"test*case","sample_rate":1,"type":"1"}`},
{"a?test*", "a-test-case", regexp.MustCompile("^a.test.*$"), `{"service":"a?test*","sample_rate":1,"type":"1"}`},
{"a*test?", "a-test-", regexp.MustCompile("^a.*test.$"), `{"service":"a*test?","sample_rate":1,"type":"1"}`},
{"a*test?case", "a--test-case", regexp.MustCompile("^a.*test.case$"), `{"service":"a*test?case","sample_rate":1,"type":"1"}`},
{"a?test*case", "a-testing--case", regexp.MustCompile("^a.test.*case$"), `{"service":"a?test*case","sample_rate":1,"type":"1"}`},
{"?test*", "atest", regexp.MustCompile("(?i)^.test.*$"), `{"service":"?test*","sample_rate":1,"type":"1"}`},
{"test*case", "testcase", regexp.MustCompile("(?i)^test.*case$"), `{"service":"test*case","sample_rate":1,"type":"1"}`},
{"a?test*", "a-test-case", regexp.MustCompile("(?i)^a.test.*$"), `{"service":"a?test*","sample_rate":1,"type":"1"}`},
{"a*test?", "a-test-", regexp.MustCompile("(?i)^a.*test.$"), `{"service":"a*test?","sample_rate":1,"type":"1"}`},
{"a*test?case", "a--test-case", regexp.MustCompile("(?i)^a.*test.case$"), `{"service":"a*test?case","sample_rate":1,"type":"1"}`},
{"a?test*case", "a-testing--case", regexp.MustCompile("(?i)^a.test.*case$"), `{"service":"a?test*case","sample_rate":1,"type":"1"}`},
//// valid non-glob regex regexp.MustCompile(), ``, pattern
{"*/*", `a/123`, regexp.MustCompile("^.*/.*$"), `{"service":"*/*","sample_rate":1,"type":"1"}`},
{`*\/*`, `a\/123`, regexp.MustCompile("^.*/.*$"), `{"service":"*/*","sample_rate":1,"type":"1"}`},
{"*/*", `a/123`, regexp.MustCompile("(?i)^.*/.*$"), `{"service":"*/*","sample_rate":1,"type":"1"}`},
{`*\/*`, `a\/123`, regexp.MustCompile("(?i)^.*/.*$"), `{"service":"*/*","sample_rate":1,"type":"1"}`},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
// the goal of this test is
Expand Down
Loading