sampling.go (8683B)
1 // Copyright The OpenTelemetry 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 15 package trace // import "go.opentelemetry.io/otel/sdk/trace" 16 17 import ( 18 "context" 19 "encoding/binary" 20 "fmt" 21 22 "go.opentelemetry.io/otel/attribute" 23 "go.opentelemetry.io/otel/trace" 24 ) 25 26 // Sampler decides whether a trace should be sampled and exported. 27 type Sampler interface { 28 // DO NOT CHANGE: any modification will not be backwards compatible and 29 // must never be done outside of a new major release. 30 31 // ShouldSample returns a SamplingResult based on a decision made from the 32 // passed parameters. 33 ShouldSample(parameters SamplingParameters) SamplingResult 34 // DO NOT CHANGE: any modification will not be backwards compatible and 35 // must never be done outside of a new major release. 36 37 // Description returns information describing the Sampler. 38 Description() string 39 // DO NOT CHANGE: any modification will not be backwards compatible and 40 // must never be done outside of a new major release. 41 } 42 43 // SamplingParameters contains the values passed to a Sampler. 44 type SamplingParameters struct { 45 ParentContext context.Context 46 TraceID trace.TraceID 47 Name string 48 Kind trace.SpanKind 49 Attributes []attribute.KeyValue 50 Links []trace.Link 51 } 52 53 // SamplingDecision indicates whether a span is dropped, recorded and/or sampled. 54 type SamplingDecision uint8 55 56 // Valid sampling decisions. 57 const ( 58 // Drop will not record the span and all attributes/events will be dropped. 59 Drop SamplingDecision = iota 60 61 // Record indicates the span's `IsRecording() == true`, but `Sampled` flag 62 // *must not* be set. 63 RecordOnly 64 65 // RecordAndSample has span's `IsRecording() == true` and `Sampled` flag 66 // *must* be set. 67 RecordAndSample 68 ) 69 70 // SamplingResult conveys a SamplingDecision, set of Attributes and a Tracestate. 71 type SamplingResult struct { 72 Decision SamplingDecision 73 Attributes []attribute.KeyValue 74 Tracestate trace.TraceState 75 } 76 77 type traceIDRatioSampler struct { 78 traceIDUpperBound uint64 79 description string 80 } 81 82 func (ts traceIDRatioSampler) ShouldSample(p SamplingParameters) SamplingResult { 83 psc := trace.SpanContextFromContext(p.ParentContext) 84 x := binary.BigEndian.Uint64(p.TraceID[8:16]) >> 1 85 if x < ts.traceIDUpperBound { 86 return SamplingResult{ 87 Decision: RecordAndSample, 88 Tracestate: psc.TraceState(), 89 } 90 } 91 return SamplingResult{ 92 Decision: Drop, 93 Tracestate: psc.TraceState(), 94 } 95 } 96 97 func (ts traceIDRatioSampler) Description() string { 98 return ts.description 99 } 100 101 // TraceIDRatioBased samples a given fraction of traces. Fractions >= 1 will 102 // always sample. Fractions < 0 are treated as zero. To respect the 103 // parent trace's `SampledFlag`, the `TraceIDRatioBased` sampler should be used 104 // as a delegate of a `Parent` sampler. 105 // 106 //nolint:revive // revive complains about stutter of `trace.TraceIDRatioBased` 107 func TraceIDRatioBased(fraction float64) Sampler { 108 if fraction >= 1 { 109 return AlwaysSample() 110 } 111 112 if fraction <= 0 { 113 fraction = 0 114 } 115 116 return &traceIDRatioSampler{ 117 traceIDUpperBound: uint64(fraction * (1 << 63)), 118 description: fmt.Sprintf("TraceIDRatioBased{%g}", fraction), 119 } 120 } 121 122 type alwaysOnSampler struct{} 123 124 func (as alwaysOnSampler) ShouldSample(p SamplingParameters) SamplingResult { 125 return SamplingResult{ 126 Decision: RecordAndSample, 127 Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(), 128 } 129 } 130 131 func (as alwaysOnSampler) Description() string { 132 return "AlwaysOnSampler" 133 } 134 135 // AlwaysSample returns a Sampler that samples every trace. 136 // Be careful about using this sampler in a production application with 137 // significant traffic: a new trace will be started and exported for every 138 // request. 139 func AlwaysSample() Sampler { 140 return alwaysOnSampler{} 141 } 142 143 type alwaysOffSampler struct{} 144 145 func (as alwaysOffSampler) ShouldSample(p SamplingParameters) SamplingResult { 146 return SamplingResult{ 147 Decision: Drop, 148 Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(), 149 } 150 } 151 152 func (as alwaysOffSampler) Description() string { 153 return "AlwaysOffSampler" 154 } 155 156 // NeverSample returns a Sampler that samples no traces. 157 func NeverSample() Sampler { 158 return alwaysOffSampler{} 159 } 160 161 // ParentBased returns a composite sampler which behaves differently, 162 // based on the parent of the span. If the span has no parent, 163 // the root(Sampler) is used to make sampling decision. If the span has 164 // a parent, depending on whether the parent is remote and whether it 165 // is sampled, one of the following samplers will apply: 166 // - remoteParentSampled(Sampler) (default: AlwaysOn) 167 // - remoteParentNotSampled(Sampler) (default: AlwaysOff) 168 // - localParentSampled(Sampler) (default: AlwaysOn) 169 // - localParentNotSampled(Sampler) (default: AlwaysOff) 170 func ParentBased(root Sampler, samplers ...ParentBasedSamplerOption) Sampler { 171 return parentBased{ 172 root: root, 173 config: configureSamplersForParentBased(samplers), 174 } 175 } 176 177 type parentBased struct { 178 root Sampler 179 config samplerConfig 180 } 181 182 func configureSamplersForParentBased(samplers []ParentBasedSamplerOption) samplerConfig { 183 c := samplerConfig{ 184 remoteParentSampled: AlwaysSample(), 185 remoteParentNotSampled: NeverSample(), 186 localParentSampled: AlwaysSample(), 187 localParentNotSampled: NeverSample(), 188 } 189 190 for _, so := range samplers { 191 c = so.apply(c) 192 } 193 194 return c 195 } 196 197 // samplerConfig is a group of options for parentBased sampler. 198 type samplerConfig struct { 199 remoteParentSampled, remoteParentNotSampled Sampler 200 localParentSampled, localParentNotSampled Sampler 201 } 202 203 // ParentBasedSamplerOption configures the sampler for a particular sampling case. 204 type ParentBasedSamplerOption interface { 205 apply(samplerConfig) samplerConfig 206 } 207 208 // WithRemoteParentSampled sets the sampler for the case of sampled remote parent. 209 func WithRemoteParentSampled(s Sampler) ParentBasedSamplerOption { 210 return remoteParentSampledOption{s} 211 } 212 213 type remoteParentSampledOption struct { 214 s Sampler 215 } 216 217 func (o remoteParentSampledOption) apply(config samplerConfig) samplerConfig { 218 config.remoteParentSampled = o.s 219 return config 220 } 221 222 // WithRemoteParentNotSampled sets the sampler for the case of remote parent 223 // which is not sampled. 224 func WithRemoteParentNotSampled(s Sampler) ParentBasedSamplerOption { 225 return remoteParentNotSampledOption{s} 226 } 227 228 type remoteParentNotSampledOption struct { 229 s Sampler 230 } 231 232 func (o remoteParentNotSampledOption) apply(config samplerConfig) samplerConfig { 233 config.remoteParentNotSampled = o.s 234 return config 235 } 236 237 // WithLocalParentSampled sets the sampler for the case of sampled local parent. 238 func WithLocalParentSampled(s Sampler) ParentBasedSamplerOption { 239 return localParentSampledOption{s} 240 } 241 242 type localParentSampledOption struct { 243 s Sampler 244 } 245 246 func (o localParentSampledOption) apply(config samplerConfig) samplerConfig { 247 config.localParentSampled = o.s 248 return config 249 } 250 251 // WithLocalParentNotSampled sets the sampler for the case of local parent 252 // which is not sampled. 253 func WithLocalParentNotSampled(s Sampler) ParentBasedSamplerOption { 254 return localParentNotSampledOption{s} 255 } 256 257 type localParentNotSampledOption struct { 258 s Sampler 259 } 260 261 func (o localParentNotSampledOption) apply(config samplerConfig) samplerConfig { 262 config.localParentNotSampled = o.s 263 return config 264 } 265 266 func (pb parentBased) ShouldSample(p SamplingParameters) SamplingResult { 267 psc := trace.SpanContextFromContext(p.ParentContext) 268 if psc.IsValid() { 269 if psc.IsRemote() { 270 if psc.IsSampled() { 271 return pb.config.remoteParentSampled.ShouldSample(p) 272 } 273 return pb.config.remoteParentNotSampled.ShouldSample(p) 274 } 275 276 if psc.IsSampled() { 277 return pb.config.localParentSampled.ShouldSample(p) 278 } 279 return pb.config.localParentNotSampled.ShouldSample(p) 280 } 281 return pb.root.ShouldSample(p) 282 } 283 284 func (pb parentBased) Description() string { 285 return fmt.Sprintf("ParentBased{root:%s,remoteParentSampled:%s,"+ 286 "remoteParentNotSampled:%s,localParentSampled:%s,localParentNotSampled:%s}", 287 pb.root.Description(), 288 pb.config.remoteParentSampled.Description(), 289 pb.config.remoteParentNotSampled.Description(), 290 pb.config.localParentSampled.Description(), 291 pb.config.localParentNotSampled.Description(), 292 ) 293 }