Skip to content

Commit 7c09a19

Browse files
fix boundary sampler so it is true to given rate (#226)
Fixes #225 Solution works accurately on both amd64 and arm64 architectures. Adds test to ensure number of sampling decisions falls within a given range. Avoid float to int conversion which is platform dependent for high values (such as trace IDs) --------- Co-authored-by: Adrian Cole <[email protected]>
1 parent e609ce4 commit 7c09a19

File tree

2 files changed

+33
-17
lines changed

2 files changed

+33
-17
lines changed

sample.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,15 @@ func NewBoundarySampler(rate float64, salt int64) (Sampler, error) {
6161
}
6262

6363
var (
64-
boundary = int64(rate * 10000)
64+
// convert rate into a proportional boundary where values below it are sampled
65+
// (e.g., 1% rate ≈ first 1% of space)
66+
boundary = uint64(rate * (1 << 63))
6567
usalt = uint64(salt)
6668
)
6769
return func(id uint64) bool {
68-
return int64(math.Abs(float64(id^usalt)))%10000 < boundary
70+
// XOR with salt provides deterministic randomization
71+
// right shift ensures uniform distribution across [0, 2^63)
72+
return ((id ^ usalt) >> 1) < boundary
6973
}, nil
7074
}
7175

sample_test.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,16 @@ func TestBoundarySampler(t *testing.T) {
3131
hasError bool
3232
}
3333
for input, sampled := range map[triple]bool{
34-
{123, 456, 1.0, false}: true,
35-
{123, 456, 999, true}: true,
36-
{123, 456, 0.0, false}: false,
37-
{123, 456, -42, true}: false,
38-
{1229998, 0, 0.01, false}: false,
39-
{1229999, 0, 0.01, false}: false,
40-
{1230000, 0, 0.01, false}: true,
41-
{1230001, 0, 0.01, false}: true,
42-
{1230098, 0, 0.01, false}: true,
43-
{1230099, 0, 0.01, false}: true,
44-
{1230100, 0, 0.01, false}: false,
45-
{1230101, 0, 0.01, false}: false,
46-
{1, 9999999, 0.01, false}: false,
47-
{999, 0, 0.99, false}: true,
48-
{9999, 0, 0.99, false}: false,
34+
{123, 456, 1.0, false}: true,
35+
{123, 456, 999, true}: true,
36+
{123, 456, 0.0, false}: false,
37+
{123, 456, -42, true}: false,
38+
{0xffffffffffffffff, 0, 0.01, false}: false,
39+
{0xa000000000000000, 0, 0.01, false}: false,
40+
{0x028f5c28f5c28f5f, 0, 0.01, false}: true,
41+
{0x028f5c28f5c28f60, 0, 0.01, false}: false,
42+
{1, 0xfffffffffffffff, 0.01, false}: false,
43+
{999, 0, 0.99, false}: true,
4944
} {
5045
sampler, err := zipkin.NewBoundarySampler(input.rate, input.salt)
5146
if want, have := input.hasError, (err != nil); want != have {
@@ -61,6 +56,23 @@ func TestBoundarySampler(t *testing.T) {
6156
if want, have := sampled, sampler(input.id); want != have {
6257
t.Errorf("%#+v: want %v, have %v", input, want, have)
6358
}
59+
60+
}
61+
}
62+
63+
func TestBoundarySamplerProducesSamplingDecisionsTrueToTheRate(t *testing.T) {
64+
rand.Uint64()
65+
c := 0
66+
sampler, _ := zipkin.NewBoundarySampler(0.01, 0)
67+
n := 10000
68+
for i := 0; i < n; i++ {
69+
id := rand.Uint64()
70+
if sampler(id) {
71+
c++
72+
}
73+
}
74+
if !(c > 50 && c < 150) {
75+
t.Error("should sample at 1%, should be in vicinity of 100")
6476
}
6577
}
6678

0 commit comments

Comments
 (0)