mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2024-12-04 09:43:23 +02:00
694c9a413d
* Interface stability documentation * Update versioning policy * Update CONTRIBUTING * Document how to extend immutable interfaces * Markdown format VERSIONING changes
289 lines
8.3 KiB
Go
289 lines
8.3 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
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
|
|
|
|
// Record indicates the span's `IsRecording() == true`, but `Sampled` flag
|
|
// *must not* be set
|
|
RecordOnly
|
|
|
|
// RecordAndSample has span's `IsRecording() == true` and `Sampled` 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[0:8]) >> 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 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),
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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)
|
|
}
|
|
|
|
// 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) {
|
|
config.remoteParentSampled = o.s
|
|
}
|
|
|
|
// 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) {
|
|
config.remoteParentNotSampled = o.s
|
|
}
|
|
|
|
// 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) {
|
|
config.localParentSampled = o.s
|
|
}
|
|
|
|
// 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) {
|
|
config.localParentNotSampled = o.s
|
|
}
|
|
|
|
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(),
|
|
)
|
|
}
|