// Package random implements a fixed-rate head sampler. It emits each
// record with probability Rate and drops the rest.
//
// See mercemay.top/src/lambdalog/internal/sampler/random/.
package random
import (
"math/rand"
"sync"
"time"
"mercemay.top/src/lambdalog/internal/sampler"
)
// Sampler is a fixed-rate head sampler. The zero value samples at rate 1.0.
type Sampler struct {
// Rate is the probability of emission in [0.0, 1.0]. Values outside the
// range are clamped on Sample.
Rate float64
// Tag, if true, returns sampler.Tag instead of sampler.Emit so kept
// records carry a sampled marker.
Tag bool
mu sync.Mutex
rng *rand.Rand
}
// Name returns a stable identifier for diagnostics.
func (s *Sampler) Name() string { return "random" }
// Sample applies the head sampling decision.
func (s *Sampler) Sample(in sampler.Input) sampler.Decision {
rate := s.Rate
if rate <= 0 {
return sampler.Drop
}
if rate >= 1 {
if s.Tag {
return sampler.Tag
}
return sampler.Emit
}
s.mu.Lock()
if s.rng == nil {
s.rng = rand.New(rand.NewSource(seedFrom(in.Now)))
}
v := s.rng.Float64()
s.mu.Unlock()
if v > rate {
return sampler.Drop
}
if s.Tag {
return sampler.Tag
}
return sampler.Emit
}
// SeedFixed sets the RNG to a deterministic seed. Intended for tests.
func (s *Sampler) SeedFixed(seed int64) {
s.mu.Lock()
s.rng = rand.New(rand.NewSource(seed))
s.mu.Unlock()
}
func seedFrom(t time.Time) int64 {
if t.IsZero() {
return time.Now().UnixNano()
}
return t.UnixNano()
}
// NewPercent returns a Sampler with Rate equal to pct/100. Out-of-range
// inputs are clamped.
func NewPercent(pct int) *Sampler {
switch {
case pct <= 0:
return &Sampler{Rate: 0}
case pct >= 100:
return &Sampler{Rate: 1}
default:
return &Sampler{Rate: float64(pct) / 100.0}
}
}