sample.go 3.6 KB

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