Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec Conformance Review: Built-in samplers #1651

Closed
2 tasks done
MrAlias opened this issue Mar 5, 2021 · 5 comments
Closed
2 tasks done

Spec Conformance Review: Built-in samplers #1651

MrAlias opened this issue Mar 5, 2021 · 5 comments
Assignees
Labels
area:trace Part of OpenTelemetry tracing pkg:SDK Related to an SDK package
Milestone

Comments

@MrAlias
Copy link
Contributor

MrAlias commented Mar 5, 2021

  • Review Built-in samplers section of the specification and identify all normative requirements it contains.
  • Validate our implementation of the specification to conform or not. If it conforms, document the conformity here. If it does not open an Issue or PR track the work.
@MrAlias MrAlias added pkg:SDK Related to an SDK package area:trace Part of OpenTelemetry tracing labels Mar 5, 2021
@MrAlias MrAlias self-assigned this Mar 9, 2021
@MrAlias
Copy link
Contributor Author

MrAlias commented Mar 9, 2021

Built-in samplers

OpenTelemetry supports a number of built-in samplers to choose from.
The default sampler is ParentBased(root=AlwaysOn).

DefaultSampler: ParentBased(AlwaysSample()),

@MrAlias
Copy link
Contributor Author

MrAlias commented Mar 9, 2021

AlwaysOn

// 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{}
}

  • Returns RECORD_AND_SAMPLE always.

Decision: RecordAndSample,

  • Description MUST be AlwaysOnSampler.

return "AlwaysOnSampler"

@MrAlias
Copy link
Contributor Author

MrAlias commented Mar 9, 2021

AlwaysOff

// NeverSample returns a Sampler that samples no traces.
func NeverSample() Sampler {
return alwaysOffSampler{}
}

  • Returns DROP always.

Decision: Drop,

  • Description MUST be AlwaysOffSampler.

return "AlwaysOffSampler"

@MrAlias
Copy link
Contributor Author

MrAlias commented Mar 9, 2021

TraceIdRatioBased

// 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:golint // golint 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),
}
}

  • The TraceIdRatioBased MUST ignore the parent SampledFlag.

It does:

func (ts traceIDRatioSampler) ShouldSample(p SamplingParameters) SamplingResult {
x := binary.BigEndian.Uint64(p.TraceID[0:8]) >> 1
if x < ts.traceIDUpperBound {
return SamplingResult{
Decision: RecordAndSample,
Tracestate: p.ParentContext.TraceState(),
}
}
return SamplingResult{
Decision: Drop,
Tracestate: p.ParentContext.TraceState(),
}
}

To respect the parent SampledFlag, the TraceIdRatioBased should be used as a delegate of the ParentBased sampler specified below.

  • Description MUST be TraceIdRatioBased{0.000100}.

I'm not sure if the specification is stating this needs to be the static string "TraceIdRatioBased{0.000100}" or if the tracing ratio needs to inserted in the curly braces. If the former we our out of compliance:

description: fmt.Sprintf("TraceIDRatioBased{%g}", fraction),

Otherwise, we need to clarify to what precision we should be representing the ratio as.

I've opened this issue in the specification repository to clarify. Tracking our follow on to that issues' resolution here.

Requirements for TraceIdRatioBased sampler algorithm
  • The sampling algorithm MUST be deterministic.

It is:

if x < ts.traceIDUpperBound {
return SamplingResult{
Decision: RecordAndSample,
Tracestate: p.ParentContext.TraceState(),
}
}

if the trace ID is within a certain bound it will be sampled otherwise it will not. That bound does not change for the entirety of the samplers life.

A trace identified by a given TraceId is sampled or not independent of language, time, etc.

Trace ID is the only parameter used in the decision, regardless of the things listed.

To achieve this, implementations MUST use a deterministic hash of the TraceId when computing the sampling decision.
By ensuring this, running the sampler on any child Span will produce the same decision.

The hash algorithm used looks at the first 8 bytes of a trace ID bit shifted right by one. This value is evaluated as a uint64 against the fraction of all numbers a uint64 can represent ([0, (2^64)-1]). If it is inclusive of this fraction it is sampled, otherwise it is dropped.

x := binary.BigEndian.Uint64(p.TraceID[0:8]) >> 1
if x < ts.traceIDUpperBound {

  • A TraceIdRatioBased sampler with a given sampling rate MUST also sample all traces that any TraceIdRatioBased sampler with a lower sampling rate would sample.
    This is important when a backend system may want to run with a higher sampling rate than the frontend system, this way all frontend traces will still be sampled and extra traces will be sampled on the backend only.

The fractional cutoff the sampler uses is inclusive of all samplers with fractions smaller than it.

traceIDUpperBound: uint64(fraction * (1 << 63)),

@MrAlias
Copy link
Contributor Author

MrAlias commented Mar 9, 2021

ParentBased

// ParentBased returns a composite sampler which behaves differently,
// based on the parent of the span. If the span has no parent,
// the root(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),
}
}

  • This is a composite sampler. ParentBased helps distinguished between the following cases:
    • No parent (root span).
    • Remote parent (SpanContext.IsRemote() == true) with SampledFlag equals true
    • Remote parent (SpanContext.IsRemote() == true) with SampledFlag equals false
    • Local parent (SpanContext.IsRemote() == false) with SampledFlag equals true
    • Local parent (SpanContext.IsRemote() == false) with SampledFlag equals false

Required parameters:

  • root(Sampler) - Sampler called for spans with no parent (root spans)

Root is a required argument of this samplers creation function:

func ParentBased(root Sampler, samplers ...ParentBasedSamplerOption) Sampler {

Optional parameters:

  • remoteParentSampled(Sampler) (default: AlwaysOn)

// WithRemoteParentSampled sets the sampler for the case of sampled remote parent.
func WithRemoteParentSampled(s Sampler) ParentBasedSamplerOption {
return remoteParentSampledOption{s}
}

  • remoteParentNotSampled(Sampler) (default: AlwaysOff)

// WithRemoteParentNotSampled sets the sampler for the case of remote parent
// which is not sampled.
func WithRemoteParentNotSampled(s Sampler) ParentBasedSamplerOption {
return remoteParentNotSampledOption{s}
}

  • localParentSampled(Sampler) (default: AlwaysOn)

// WithLocalParentSampled sets the sampler for the case of sampled local parent.
func WithLocalParentSampled(s Sampler) ParentBasedSamplerOption {
return localParentSampledOption{s}
}

  • localParentNotSampled(Sampler) (default: AlwaysOff)

// WithLocalParentNotSampled sets the sampler for the case of local parent
// which is not sampled.
func WithLocalParentNotSampled(s Sampler) ParentBasedSamplerOption {
return localParentNotSampledOption{s}
}

@MrAlias MrAlias closed this as completed Mar 9, 2021
@pellared pellared added this to the untracked milestone Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:trace Part of OpenTelemetry tracing pkg:SDK Related to an SDK package
Projects
None yet
Development

No branches or pull requests

2 participants