diff --git a/ddtrace/tracer/sampler.go b/ddtrace/tracer/sampler.go index 0263d7206d5..3568829ca46 100644 --- a/ddtrace/tracer/sampler.go +++ b/ddtrace/tracer/sampler.go @@ -6,6 +6,7 @@ package tracer import ( + "bytes" "encoding/json" "io" "math" @@ -121,9 +122,24 @@ 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. It uses AppendFloat with a +// stack-allocated buffer to avoid heap allocations. func formatKnuthSamplingRate(rate float64) string { - return strconv.FormatFloat(rate, 'g', 6, 64) + 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, diff --git a/ddtrace/tracer/sampler_test.go b/ddtrace/tracer/sampler_test.go index 5e990adae9c..b93aede2606 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 { @@ -2260,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