| // Copyright The OpenTelemetry Authors |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| package trace // import "go.opentelemetry.io/otel/sdk/trace" |
| |
| import ( |
| "context" |
| "encoding/binary" |
| "fmt" |
| |
| "go.opentelemetry.io/otel/attribute" |
| "go.opentelemetry.io/otel/trace" |
| ) |
| |
| // Sampler decides whether a trace should be sampled and exported. |
| type Sampler interface { |
| // DO NOT CHANGE: any modification will not be backwards compatible and |
| // must never be done outside of a new major release. |
| |
| // ShouldSample returns a SamplingResult based on a decision made from the |
| // passed parameters. |
| ShouldSample(parameters SamplingParameters) SamplingResult |
| // DO NOT CHANGE: any modification will not be backwards compatible and |
| // must never be done outside of a new major release. |
| |
| // Description returns information describing the Sampler. |
| Description() string |
| // DO NOT CHANGE: any modification will not be backwards compatible and |
| // must never be done outside of a new major release. |
| } |
| |
| // SamplingParameters contains the values passed to a Sampler. |
| type SamplingParameters struct { |
| ParentContext context.Context |
| TraceID trace.TraceID |
| Name string |
| Kind trace.SpanKind |
| Attributes []attribute.KeyValue |
| Links []trace.Link |
| } |
| |
| // SamplingDecision indicates whether a span is dropped, recorded and/or sampled. |
| type SamplingDecision uint8 |
| |
| // Valid sampling decisions. |
| const ( |
| // Drop will not record the span and all attributes/events will be dropped. |
| Drop SamplingDecision = iota |
| |
| // RecordOnly indicates the span's IsRecording method returns true, but trace.FlagsSampled flag |
| // must not be set. |
| RecordOnly |
| |
| // RecordAndSample indicates the span's IsRecording method returns true and trace.FlagsSampled flag |
| // must be set. |
| RecordAndSample |
| ) |
| |
| // SamplingResult conveys a SamplingDecision, set of Attributes and a Tracestate. |
| type SamplingResult struct { |
| Decision SamplingDecision |
| Attributes []attribute.KeyValue |
| Tracestate trace.TraceState |
| } |
| |
| type traceIDRatioSampler struct { |
| traceIDUpperBound uint64 |
| description string |
| } |
| |
| func (ts traceIDRatioSampler) ShouldSample(p SamplingParameters) SamplingResult { |
| psc := trace.SpanContextFromContext(p.ParentContext) |
| x := binary.BigEndian.Uint64(p.TraceID[8:16]) >> 1 |
| if x < ts.traceIDUpperBound { |
| return SamplingResult{ |
| Decision: RecordAndSample, |
| Tracestate: psc.TraceState(), |
| } |
| } |
| return SamplingResult{ |
| Decision: Drop, |
| Tracestate: psc.TraceState(), |
| } |
| } |
| |
| func (ts traceIDRatioSampler) Description() string { |
| return ts.description |
| } |
| |
| // TraceIDRatioBased samples a given fraction of traces. Fractions >= 1 will |
| // always sample. Fractions < 0 are treated as zero. To respect the |
| // parent trace's `SampledFlag`, the `TraceIDRatioBased` sampler should be used |
| // as a delegate of a `Parent` sampler. |
| // |
| //nolint:revive // revive complains about stutter of `trace.TraceIDRatioBased` |
| func TraceIDRatioBased(fraction float64) Sampler { |
| if fraction >= 1 { |
| return AlwaysSample() |
| } |
| |
| if fraction <= 0 { |
| fraction = 0 |
| } |
| |
| return &traceIDRatioSampler{ |
| traceIDUpperBound: uint64(fraction * (1 << 63)), |
| description: fmt.Sprintf("TraceIDRatioBased{%g}", fraction), |
| } |
| } |
| |
| type alwaysOnSampler struct{} |
| |
| func (as alwaysOnSampler) ShouldSample(p SamplingParameters) SamplingResult { |
| return SamplingResult{ |
| Decision: RecordAndSample, |
| Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(), |
| } |
| } |
| |
| func (as alwaysOnSampler) Description() string { |
| return "AlwaysOnSampler" |
| } |
| |
| // AlwaysSample returns a Sampler that samples every trace. |
| // Be careful about using this sampler in a production application with |
| // significant traffic: a new trace will be started and exported for every |
| // request. |
| func AlwaysSample() Sampler { |
| return alwaysOnSampler{} |
| } |
| |
| type alwaysOffSampler struct{} |
| |
| func (as alwaysOffSampler) ShouldSample(p SamplingParameters) SamplingResult { |
| return SamplingResult{ |
| Decision: Drop, |
| Tracestate: trace.SpanContextFromContext(p.ParentContext).TraceState(), |
| } |
| } |
| |
| func (as alwaysOffSampler) Description() string { |
| return "AlwaysOffSampler" |
| } |
| |
| // NeverSample returns a Sampler that samples no traces. |
| func NeverSample() Sampler { |
| return alwaysOffSampler{} |
| } |
| |
| // ParentBased returns a sampler decorator which behaves differently, |
| // based on the parent of the span. If the span has no parent, |
| // the decorated sampler is used to make sampling decision. If the span has |
| // a parent, depending on whether the parent is remote and whether it |
| // is sampled, one of the following samplers will apply: |
| // - remoteParentSampled(Sampler) (default: AlwaysOn) |
| // - remoteParentNotSampled(Sampler) (default: AlwaysOff) |
| // - localParentSampled(Sampler) (default: AlwaysOn) |
| // - localParentNotSampled(Sampler) (default: AlwaysOff) |
| func ParentBased(root Sampler, samplers ...ParentBasedSamplerOption) Sampler { |
| return parentBased{ |
| root: root, |
| config: configureSamplersForParentBased(samplers), |
| } |
| } |
| |
| type parentBased struct { |
| root Sampler |
| config samplerConfig |
| } |
| |
| func configureSamplersForParentBased(samplers []ParentBasedSamplerOption) samplerConfig { |
| c := samplerConfig{ |
| remoteParentSampled: AlwaysSample(), |
| remoteParentNotSampled: NeverSample(), |
| localParentSampled: AlwaysSample(), |
| localParentNotSampled: NeverSample(), |
| } |
| |
| for _, so := range samplers { |
| c = so.apply(c) |
| } |
| |
| return c |
| } |
| |
| // samplerConfig is a group of options for parentBased sampler. |
| type samplerConfig struct { |
| remoteParentSampled, remoteParentNotSampled Sampler |
| localParentSampled, localParentNotSampled Sampler |
| } |
| |
| // ParentBasedSamplerOption configures the sampler for a particular sampling case. |
| type ParentBasedSamplerOption interface { |
| apply(samplerConfig) samplerConfig |
| } |
| |
| // WithRemoteParentSampled sets the sampler for the case of sampled remote parent. |
| func WithRemoteParentSampled(s Sampler) ParentBasedSamplerOption { |
| return remoteParentSampledOption{s} |
| } |
| |
| type remoteParentSampledOption struct { |
| s Sampler |
| } |
| |
| func (o remoteParentSampledOption) apply(config samplerConfig) samplerConfig { |
| config.remoteParentSampled = o.s |
| return config |
| } |
| |
| // WithRemoteParentNotSampled sets the sampler for the case of remote parent |
| // which is not sampled. |
| func WithRemoteParentNotSampled(s Sampler) ParentBasedSamplerOption { |
| return remoteParentNotSampledOption{s} |
| } |
| |
| type remoteParentNotSampledOption struct { |
| s Sampler |
| } |
| |
| func (o remoteParentNotSampledOption) apply(config samplerConfig) samplerConfig { |
| config.remoteParentNotSampled = o.s |
| return config |
| } |
| |
| // WithLocalParentSampled sets the sampler for the case of sampled local parent. |
| func WithLocalParentSampled(s Sampler) ParentBasedSamplerOption { |
| return localParentSampledOption{s} |
| } |
| |
| type localParentSampledOption struct { |
| s Sampler |
| } |
| |
| func (o localParentSampledOption) apply(config samplerConfig) samplerConfig { |
| config.localParentSampled = o.s |
| return config |
| } |
| |
| // WithLocalParentNotSampled sets the sampler for the case of local parent |
| // which is not sampled. |
| func WithLocalParentNotSampled(s Sampler) ParentBasedSamplerOption { |
| return localParentNotSampledOption{s} |
| } |
| |
| type localParentNotSampledOption struct { |
| s Sampler |
| } |
| |
| func (o localParentNotSampledOption) apply(config samplerConfig) samplerConfig { |
| config.localParentNotSampled = o.s |
| return config |
| } |
| |
| func (pb parentBased) ShouldSample(p SamplingParameters) SamplingResult { |
| psc := trace.SpanContextFromContext(p.ParentContext) |
| if psc.IsValid() { |
| if psc.IsRemote() { |
| if psc.IsSampled() { |
| return pb.config.remoteParentSampled.ShouldSample(p) |
| } |
| return pb.config.remoteParentNotSampled.ShouldSample(p) |
| } |
| |
| if psc.IsSampled() { |
| return pb.config.localParentSampled.ShouldSample(p) |
| } |
| return pb.config.localParentNotSampled.ShouldSample(p) |
| } |
| return pb.root.ShouldSample(p) |
| } |
| |
| func (pb parentBased) Description() string { |
| return fmt.Sprintf("ParentBased{root:%s,remoteParentSampled:%s,"+ |
| "remoteParentNotSampled:%s,localParentSampled:%s,localParentNotSampled:%s}", |
| pb.root.Description(), |
| pb.config.remoteParentSampled.Description(), |
| pb.config.remoteParentNotSampled.Description(), |
| pb.config.localParentSampled.Description(), |
| pb.config.localParentNotSampled.Description(), |
| ) |
| } |