From 716c10b7f657ac25604117e0a14a2ea98c40b0fe Mon Sep 17 00:00:00 2001 From: bm1549 Date: Wed, 25 Mar 2026 20:34:04 -0400 Subject: [PATCH 1/3] fix(tracer): use fixed-point format for _dd.p.ksr tag formatKnuthSamplingRate was using 'g' (significant digits) format which produces scientific notation for very small rates (e.g. "1e-06" instead of "0.000001"). Switch to 'f' (fixed-point) format with 6 decimal places to match the cross-language spec enforced by system tests. Co-Authored-By: Claude Opus 4.6 (1M context) --- ddtrace/tracer/sampler.go | 8 ++++++-- ddtrace/tracer/sampler_test.go | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ddtrace/tracer/sampler.go b/ddtrace/tracer/sampler.go index 0263d7206d5..527f4ea7518 100644 --- a/ddtrace/tracer/sampler.go +++ b/ddtrace/tracer/sampler.go @@ -121,9 +121,13 @@ func sampledByRate(n uint64, rate float64) bool { return n*knuthFactor <= uint64(rate*math.MaxUint64) } -// formatKnuthSamplingRate formats a sampling rate as a string with up to 6 decimal digits +// formatKnuthSamplingRate formats a sampling rate as a string with up to 6 +// decimal places, trimming trailing zeros. func formatKnuthSamplingRate(rate float64) string { - return strconv.FormatFloat(rate, 'g', 6, 64) + s := strconv.FormatFloat(rate, 'f', 6, 64) + s = strings.TrimRight(s, "0") + s = strings.TrimRight(s, ".") + return s } // serviceEnvKey is used as a map key for per-service sampling rates, diff --git a/ddtrace/tracer/sampler_test.go b/ddtrace/tracer/sampler_test.go index 5e990adae9c..a002df7aa43 100644 --- a/ddtrace/tracer/sampler_test.go +++ b/ddtrace/tracer/sampler_test.go @@ -2227,6 +2227,10 @@ func TestKnuthSamplingRateWithFloatRules(t *testing.T) { {"six_decimals", 0.123456, "0.123456"}, {"seven_decimals_rounded", 0.1234567, "0.123457"}, {"trailing_zeros", 0.100000, "0.1"}, + {"rate_1_strips_trailing_zeros", 1.0, "1"}, + {"six_decimal_precision_boundary", 0.000001, "0.000001"}, + {"below_precision_rounds_to_zero", 0.0000001, "0"}, + {"rounds_up_to_one_millionth", 0.00000051, "0.000001"}, } for _, tc := range testCases { From b6922baf77b03a11fbbad469efcb2e3089bbb9e0 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Thu, 26 Mar 2026 08:47:39 -0400 Subject: [PATCH 2/3] bench(tracer): add benchmark for formatKnuthSamplingRate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requested by reviewer to compare old ('g') vs new ('f') implementation. Results show ~5ns/op overhead (35→40 ns/op), 1 extra alloc (8B), which is negligible since this runs once per root span. Co-Authored-By: Claude Opus 4.6 (1M context) --- ddtrace/tracer/sampler_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ddtrace/tracer/sampler_test.go b/ddtrace/tracer/sampler_test.go index a002df7aa43..b93aede2606 100644 --- a/ddtrace/tracer/sampler_test.go +++ b/ddtrace/tracer/sampler_test.go @@ -2264,6 +2264,14 @@ func TestKnuthSamplingRateWithFloatRules(t *testing.T) { } } +func BenchmarkFormatKnuthSamplingRate(b *testing.B) { + rates := []float64{1.0, 0.5, 0.000001, 0.0000001, 0.00000051, 0.7654321} + b.ResetTimer() + for i := 0; i < b.N; i++ { + formatKnuthSamplingRate(rates[i%len(rates)]) + } +} + func TestCappedRate(t *testing.T) { tests := []struct { name string From db7b27f5803fdceebf9015297dc53518512cba16 Mon Sep 17 00:00:00 2001 From: bm1549 Date: Thu, 26 Mar 2026 09:13:47 -0400 Subject: [PATCH 3/3] perf(tracer): eliminate allocation in formatKnuthSamplingRate Use strconv.AppendFloat with a stack-allocated [24]byte buffer and in-place trimming instead of FormatFloat + strings.TrimRight, which was creating an extra heap allocation. The new implementation has 0 allocs/op matching the original 'g' format implementation. Co-Authored-By: Claude Opus 4.6 (1M context) --- ddtrace/tracer/sampler.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ddtrace/tracer/sampler.go b/ddtrace/tracer/sampler.go index 527f4ea7518..3568829ca46 100644 --- a/ddtrace/tracer/sampler.go +++ b/ddtrace/tracer/sampler.go @@ -6,6 +6,7 @@ package tracer import ( + "bytes" "encoding/json" "io" "math" @@ -122,12 +123,23 @@ func sampledByRate(n uint64, rate float64) bool { } // formatKnuthSamplingRate formats a sampling rate as a string with up to 6 -// decimal places, trimming trailing zeros. +// decimal places, trimming trailing zeros. It uses AppendFloat with a +// stack-allocated buffer to avoid heap allocations. func formatKnuthSamplingRate(rate float64) string { - s := strconv.FormatFloat(rate, 'f', 6, 64) - s = strings.TrimRight(s, "0") - s = strings.TrimRight(s, ".") - return s + var buf [24]byte + b := strconv.AppendFloat(buf[:0], rate, 'f', 6, 64) + // Trim trailing zeros after decimal point, then the dot itself if needed. + if i := bytes.IndexByte(b, '.'); i >= 0 { + end := len(b) + for end > i+1 && b[end-1] == '0' { + end-- + } + if b[end-1] == '.' { + end-- + } + b = b[:end] + } + return string(b) } // serviceEnvKey is used as a map key for per-service sampling rates,