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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
// Package consistent provides a consistent probability based sampler.
package consistent // import "go.opentelemetry.io/contrib/samplers/probability/consistent"
import (
"fmt"
"math/bits"
"math/rand"
"sync"
"go.opentelemetry.io/otel"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
type (
// ProbabilityBasedOption is an option to the
// ConssitentProbabilityBased sampler.
ProbabilityBasedOption interface {
apply(*consistentProbabilityBasedConfig)
}
consistentProbabilityBasedConfig struct {
source rand.Source
}
consistentProbabilityBasedRandomSource struct {
rand.Source
}
consistentProbabilityBased struct {
// "LAC" is an abbreviation for the logarithm of
// adjusted count. Greater values have greater
// representivity, therefore lesser sampling
// probability.
// lowLAC is the lower-probability log-adjusted count
lowLAC uint8
// highLAC is the higher-probability log-adjusted
// count. except for the zero probability special
// case, highLAC == lowLAC - 1.
highLAC uint8
// lowProb is the probability that lowLAC should be used,
// in the interval (0, 1]. For exact powers of two and the
// special case of 0 probability, lowProb == 1.
lowProb float64
// lock protects rnd
lock sync.Mutex
rnd *rand.Rand
}
)
// WithRandomSource sets the source of the randomness used by the Sampler.
func WithRandomSource(source rand.Source) ProbabilityBasedOption {
return consistentProbabilityBasedRandomSource{source}
}
func (s consistentProbabilityBasedRandomSource) apply(cfg *consistentProbabilityBasedConfig) {
cfg.source = s.Source
}
// ProbabilityBased samples a given fraction of traces. Based on the
// OpenTelemetry specification, this Sampler supports only power-of-two
// fractions. When the input fraction is not a power of two, it will
// be rounded down.
// - Fractions >= 1 will always sample.
// - Fractions < 2^-62 are treated as zero.
//
// This Sampler sets the OpenTelemetry tracestate p-value and/or r-value.
//
// To respect the parent trace's `SampledFlag`, this sampler should be
// used as the root delegate of a `Parent` sampler.
func ProbabilityBased(fraction float64, opts ...ProbabilityBasedOption) sdktrace.Sampler {
cfg := consistentProbabilityBasedConfig{
source: rand.NewSource(rand.Int63()), //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive.
}
for _, opt := range opts {
opt.apply(&cfg)
}
if fraction < 0 {
fraction = 0
} else if fraction > 1 {
fraction = 1
}
lowLAC, highLAC, lowProb := splitProb(fraction)
return &consistentProbabilityBased{
lowLAC: lowLAC,
highLAC: highLAC,
lowProb: lowProb,
rnd: rand.New(cfg.source), //nolint:gosec // G404: Use of weak random number generator (math/rand instead of crypto/rand) is ignored as this is not security-sensitive.
}
}
func (cs *consistentProbabilityBased) newR() uint8 {
cs.lock.Lock()
defer cs.lock.Unlock()
return uint8(bits.LeadingZeros64(uint64(cs.rnd.Int63())) - 1) // nolint: gosec // 8-bit sample.
}
func (cs *consistentProbabilityBased) lowChoice() bool {
cs.lock.Lock()
defer cs.lock.Unlock()
return cs.rnd.Float64() < cs.lowProb
}
// ShouldSample implements "go.opentelemetry.io/otel/sdk/trace".Sampler.
func (cs *consistentProbabilityBased) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
psc := trace.SpanContextFromContext(p.ParentContext)
// Note: this ignores whether psc.IsValid() because this
// allows other otel trace state keys to pass through even
// for root decisions.
state := psc.TraceState()
otts, err := parseOTelTraceState(state.Get(traceStateKey), psc.IsSampled())
if err != nil {
// Note: a state.Insert(traceStateKey)
// follows, nothing else needs to be done here.
otel.Handle(err)
}
if !otts.hasRValue() {
otts.rvalue = cs.newR()
}
var decision sdktrace.SamplingDecision
var lac uint8
if cs.lowProb == 1 || cs.lowChoice() {
lac = cs.lowLAC
} else {
lac = cs.highLAC
}
if lac <= otts.rvalue {
decision = sdktrace.RecordAndSample
otts.pvalue = lac
} else {
decision = sdktrace.Drop
otts.pvalue = invalidValue
}
// Note: see the note in
// "go.opentelemetry.io/otel/trace".TraceState.Insert(). The
// error below is not a condition we're supposed to handle.
state, _ = state.Insert(traceStateKey, otts.serialize())
return sdktrace.SamplingResult{
Decision: decision,
Tracestate: state,
}
}
// Description returns "ProbabilityBased{%g}" with the configured probability.
func (cs *consistentProbabilityBased) Description() string {
var prob float64
if cs.lowLAC != pZeroValue {
prob = cs.lowProb * expToFloat64(-int(cs.lowLAC))
prob += (1 - cs.lowProb) * expToFloat64(-int(cs.highLAC))
}
return fmt.Sprintf("ProbabilityBased{%g}", prob)
}
|