-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathsample.go
131 lines (120 loc) · 3.8 KB
/
sample.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright 2022 The OpenZipkin Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package zipkin
import (
"fmt"
"math"
"math/rand"
"sync"
"time"
)
// Sampler functions return if a Zipkin span should be sampled, based on its
// traceID.
type Sampler func(id uint64) bool
// NeverSample will always return false. If used by a service it will not allow
// the service to start traces but will still allow the service to participate
// in traces started upstream.
func NeverSample(_ uint64) bool { return false }
// AlwaysSample will always return true. If used by a service it will always start
// traces if no upstream trace has been propagated. If an incoming upstream trace
// is not sampled the service will adhere to this and only propagate the context.
func AlwaysSample(_ uint64) bool { return true }
// NewModuloSampler provides a generic type Sampler.
func NewModuloSampler(mod uint64) Sampler {
if mod < 2 {
return AlwaysSample
}
return func(id uint64) bool {
return (id % mod) == 0
}
}
// NewBoundarySampler is appropriate for high-traffic instrumentation who
// provision random trace ids, and make the sampling decision only once.
// It defends against nodes in the cluster selecting exactly the same ids.
func NewBoundarySampler(rate float64, salt int64) (Sampler, error) {
if rate == 0.0 {
return NeverSample, nil
}
if rate == 1.0 {
return AlwaysSample, nil
}
if rate < 0.0001 || rate > 1 {
return nil, fmt.Errorf("rate should be 0.0 or between 0.0001 and 1: was %f", rate)
}
var (
// convert rate into a proportional boundary where values below it are sampled
// (e.g., 1% rate ≈ first 1% of space)
boundary = uint64(rate * (1 << 63))
usalt = uint64(salt)
)
return func(id uint64) bool {
// XOR with salt provides deterministic randomization
// right shift ensures uniform distribution across [0, 2^63)
return ((id ^ usalt) >> 1) < boundary
}, nil
}
// NewCountingSampler is appropriate for low-traffic instrumentation or
// those who do not provision random trace ids. It is not appropriate for
// collectors as the sampling decision isn't idempotent (consistent based
// on trace id).
func NewCountingSampler(rate float64) (Sampler, error) {
if rate == 0.0 {
return NeverSample, nil
}
if rate == 1.0 {
return AlwaysSample, nil
}
if rate < 0.01 || rate > 1 {
return nil, fmt.Errorf("rate should be 0.0 or between 0.01 and 1: was %f", rate)
}
var (
i = 0
outOf100 = int(rate*100 + math.Copysign(0.5, rate*100)) // for rounding float to int conversion instead of truncation
decisions = randomBitSet(100, outOf100, rand.New(rand.NewSource(time.Now().UnixNano())))
mtx = &sync.Mutex{}
)
return func(_ uint64) bool {
mtx.Lock()
result := decisions[i]
i++
if i == 100 {
i = 0
}
mtx.Unlock()
return result
}, nil
}
/**
* Reservoir sampling algorithm borrowed from Stack Overflow.
*
* http://stackoverflow.com/questions/12817946/generate-a-random-bitset-with-n-1s
*/
func randomBitSet(size int, cardinality int, rnd *rand.Rand) []bool {
result := make([]bool, size)
chosen := make([]int, cardinality)
var i int
for i = 0; i < cardinality; i++ {
chosen[i] = i
result[i] = true
}
for ; i < size; i++ {
j := rnd.Intn(i + 1)
if j < cardinality {
result[chosen[j]] = false
result[i] = true
chosen[j] = i
}
}
return result
}