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

Rename ParentOrElse sampler to ParentBased and enhance it according to the spec #1153

Merged
merged 6 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Move `tools` package under `internal`. (#1141)
- Move `go.opentelemetry.io/otel/api/correlation` package to `go.opentelemetry.io/otel/api/baggage`. (#1142)
The `correlation.CorrelationContext` propagator has been renamed `baggage.Baggage`. Other exported functions and types are unchanged.
- Rename `ParentOrElse` sampler to `ParentBased` and allow setting samplers depending on parent span. ()

## [0.11.0] - 2020-08-24

Expand Down
2 changes: 1 addition & 1 deletion sdk/trace/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func NewProvider(opts ...ProviderOption) *Provider {
namedTracer: make(map[instrumentation.Library]*tracer),
}
tp.config.Store(&Config{
DefaultSampler: ParentSample(AlwaysSample()),
DefaultSampler: ParentBased(AlwaysSample()),
IDGenerator: defIDGenerator(),
MaxAttributesPerSpan: DefaultMaxAttributesPerSpan,
MaxEventsPerSpan: DefaultMaxEventsPerSpan,
Expand Down
106 changes: 89 additions & 17 deletions sdk/trace/sampling.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,29 +125,101 @@ func NeverSample() Sampler {
return alwaysOffSampler{}
}

// ParentSample returns a Sampler that samples a trace only
// if the the span has a parent span and it is sampled. If the span has
// parent span but it is not sampled, neither will this span. If the span
// does not have a parent the fallback Sampler is used to determine if the
// span should be sampled.
func ParentSample(fallback Sampler) Sampler {
return parentSampler{fallback}
// 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 ...SamplerOption) Sampler {
return parentBased{
root: root,
samplers: configureSamplersForParentBased(samplers),
}
}

type parentBased struct {
root Sampler
samplers parentBasedSamplers
}

type SamplerOption func(*parentBasedSamplers)
MrAlias marked this conversation as resolved.
Show resolved Hide resolved

type parentBasedSamplers struct {
remoteParentSampled, remoteParentNotSampled Sampler
localParentSampled, localParentNotSampled Sampler
}
MrAlias marked this conversation as resolved.
Show resolved Hide resolved

func configureSamplersForParentBased(samplers []SamplerOption) parentBasedSamplers {
o := parentBasedSamplers{}
for _, s := range samplers {
s(&o)
}

if o.remoteParentSampled == nil {
o.remoteParentSampled = AlwaysSample()
}
if o.remoteParentNotSampled == nil {
o.remoteParentNotSampled = NeverSample()
}
if o.localParentSampled == nil {
o.localParentSampled = AlwaysSample()
}
if o.localParentNotSampled == nil {
o.localParentNotSampled = NeverSample()
}
MrAlias marked this conversation as resolved.
Show resolved Hide resolved

return o
}

type parentSampler struct {
fallback Sampler
func WithRemoteParentSampled(s Sampler) SamplerOption {
return func(o *parentBasedSamplers) {
o.remoteParentSampled = s
}
}
func WithRemoteParentNotSampled(s Sampler) SamplerOption {
return func(o *parentBasedSamplers) {
o.remoteParentNotSampled = s
}
}
func WithLocalParentSampled(s Sampler) SamplerOption {
return func(o *parentBasedSamplers) {
o.localParentSampled = s
}
}
func WithLocalParentNotSampled(s Sampler) SamplerOption {
return func(o *parentBasedSamplers) {
o.localParentNotSampled = s
}
}

func (ps parentSampler) ShouldSample(p SamplingParameters) SamplingResult {
func (pb parentBased) ShouldSample(p SamplingParameters) SamplingResult {
if p.ParentContext.IsValid() {
if p.HasRemoteParent {
if p.ParentContext.IsSampled() {
return pb.samplers.remoteParentSampled.ShouldSample(p)
}
return pb.samplers.remoteParentNotSampled.ShouldSample(p)
}

if p.ParentContext.IsSampled() {
return SamplingResult{Decision: RecordAndSampled}
return pb.samplers.localParentSampled.ShouldSample(p)
}
return SamplingResult{Decision: NotRecord}
return pb.samplers.localParentNotSampled.ShouldSample(p)
}
return ps.fallback.ShouldSample(p)
}

func (ps parentSampler) Description() string {
return fmt.Sprintf("ParentOrElse{%s}", ps.fallback.Description())
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.samplers.remoteParentSampled.Description(),
pb.samplers.remoteParentNotSampled.Description(),
pb.samplers.localParentSampled.Description(),
pb.samplers.localParentNotSampled.Description(),
)
}
123 changes: 102 additions & 21 deletions sdk/trace/sampling_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package trace

import (
"fmt"
"math/rand"
"testing"

Expand All @@ -23,8 +24,8 @@ import (
api "go.opentelemetry.io/otel/api/trace"
)

func TestAlwaysParentSampleWithParentSampled(t *testing.T) {
sampler := ParentSample(AlwaysSample())
func TestParentBasedDefaultLocalParentSampled(t *testing.T) {
sampler := ParentBased(AlwaysSample())
traceID, _ := api.IDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := api.SpanIDFromHex("00f067aa0ba902b7")
parentCtx := api.SpanContext{
Expand All @@ -37,22 +38,8 @@ func TestAlwaysParentSampleWithParentSampled(t *testing.T) {
}
}

func TestNeverParentSampleWithParentSampled(t *testing.T) {
sampler := ParentSample(NeverSample())
traceID, _ := api.IDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := api.SpanIDFromHex("00f067aa0ba902b7")
parentCtx := api.SpanContext{
TraceID: traceID,
SpanID: spanID,
TraceFlags: api.FlagsSampled,
}
if sampler.ShouldSample(SamplingParameters{ParentContext: parentCtx}).Decision != RecordAndSampled {
t.Error("Sampling decision should be RecordAndSampled")
}
}

func TestAlwaysParentSampleWithParentNotSampled(t *testing.T) {
sampler := ParentSample(AlwaysSample())
func TestParentBasedDefaultLocalParentNotSampled(t *testing.T) {
sampler := ParentBased(AlwaysSample())
traceID, _ := api.IDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := api.SpanIDFromHex("00f067aa0ba902b7")
parentCtx := api.SpanContext{
Expand All @@ -64,20 +51,114 @@ func TestAlwaysParentSampleWithParentNotSampled(t *testing.T) {
}
}

func TestParentSampleWithNoParent(t *testing.T) {
func TestParentBasedWithNoParent(t *testing.T) {
params := SamplingParameters{}

sampler := ParentSample(AlwaysSample())
sampler := ParentBased(AlwaysSample())
if sampler.ShouldSample(params).Decision != RecordAndSampled {
t.Error("Sampling decision should be RecordAndSampled")
}

sampler = ParentSample(NeverSample())
sampler = ParentBased(NeverSample())
if sampler.ShouldSample(params).Decision != NotRecord {
t.Error("Sampling decision should be NotRecord")
}
}

func TestParentBasedWithSamplerOptions(t *testing.T) {
testCases := []struct {
name string
samplerOption SamplerOption
isParentRemote, isParentSampled bool
expectedDecision SamplingDecision
}{
{
"localParentSampled",
WithLocalParentSampled(NeverSample()),
false,
true,
NotRecord,
},
{
"localParentNotSampled",
WithLocalParentNotSampled(AlwaysSample()),
false,
false,
RecordAndSampled,
},
{
"remoteParentSampled",
WithRemoteParentSampled(NeverSample()),
true,
true,
NotRecord,
},
{
"remoteParentNotSampled",
WithRemoteParentNotSampled(AlwaysSample()),
true,
false,
RecordAndSampled,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
traceID, _ := api.IDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
spanID, _ := api.SpanIDFromHex("00f067aa0ba902b7")
parentCtx := api.SpanContext{
TraceID: traceID,
SpanID: spanID,
}

if tc.isParentSampled {
parentCtx.TraceFlags = api.FlagsSampled
}

params := SamplingParameters{ParentContext: parentCtx}
if tc.isParentRemote {
params.HasRemoteParent = true
}

sampler := ParentBased(
nil,
tc.samplerOption,
)

switch tc.expectedDecision {
case RecordAndSampled:
if sampler.ShouldSample(params).Decision != tc.expectedDecision {
t.Error("Sampling decision should be RecordAndSampled")
}
case NotRecord:
if sampler.ShouldSample(params).Decision != tc.expectedDecision {
t.Error("Sampling decision should be NotRecord")
}
}
})
}
}

func TestParentBasedDefaultDescription(t *testing.T) {
sampler := ParentBased(AlwaysSample())

expectedDescription := fmt.Sprintf("ParentBased{root:%s,remoteParentSampled:%s,"+
"remoteParentNotSampled:%s,localParentSampled:%s,localParentNotSampled:%s}",
AlwaysSample().Description(),
AlwaysSample().Description(),
NeverSample().Description(),
AlwaysSample().Description(),
NeverSample().Description())

if sampler.Description() != expectedDescription {
t.Error(fmt.Sprintf("Sampler description should be %s, got '%s' instead",
expectedDescription,
sampler.Description(),
))
}

}

// TraceIDRatioBased sampler requirements state
// "A TraceIDRatioBased sampler with a given sampling rate MUST also sample
// all traces that any TraceIDRatioBased sampler with a lower sampling rate
Expand Down
22 changes: 11 additions & 11 deletions sdk/trace/trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,10 @@ func TestSampling(t *testing.T) {
"TraceIdRatioBased_.75": {sampler: TraceIDRatioBased(0.75), expect: .75},
"TraceIdRatioBased_2.0": {sampler: TraceIDRatioBased(2.0), expect: 1},

// Spans w/o a parent and using ParentSample(DelegateSampler()) Sampler, receive DelegateSampler's sampling decision
"ParentNeverSample": {sampler: ParentSample(NeverSample()), expect: 0},
"ParentAlwaysSample": {sampler: ParentSample(AlwaysSample()), expect: 1},
"ParentTraceIdRatioBased_.50": {sampler: ParentSample(TraceIDRatioBased(0.50)), expect: .5},
// Spans w/o a parent and using ParentBased(DelegateSampler()) Sampler, receive DelegateSampler's sampling decision
"ParentNeverSample": {sampler: ParentBased(NeverSample()), expect: 0},
"ParentAlwaysSample": {sampler: ParentBased(AlwaysSample()), expect: 1},
"ParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: .5},

// An unadorned TraceIDRatioBased sampler ignores parent spans
"UnsampledParentSpanWithTraceIdRatioBased_.25": {sampler: TraceIDRatioBased(0.25), expect: .25, parent: true},
Expand All @@ -248,13 +248,13 @@ func TestSampling(t *testing.T) {
// Spans with a sampled parent but using NeverSample Sampler, are not sampled
"SampledParentSpanWithNeverSample": {sampler: NeverSample(), expect: 0, parent: true, sampledParent: true},

// Spans with a sampled parent and using ParentSample(DelegateSampler()) Sampler, inherit the parent span's sampling status
"SampledParentSpanWithParentNeverSample": {sampler: ParentSample(NeverSample()), expect: 1, parent: true, sampledParent: true},
"UnsampledParentSpanWithParentNeverSampler": {sampler: ParentSample(NeverSample()), expect: 0, parent: true, sampledParent: false},
"SampledParentSpanWithParentAlwaysSampler": {sampler: ParentSample(AlwaysSample()), expect: 1, parent: true, sampledParent: true},
"UnsampledParentSpanWithParentAlwaysSampler": {sampler: ParentSample(AlwaysSample()), expect: 0, parent: true, sampledParent: false},
"SampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentSample(TraceIDRatioBased(0.50)), expect: 1, parent: true, sampledParent: true},
"UnsampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentSample(TraceIDRatioBased(0.50)), expect: 0, parent: true, sampledParent: false},
// Spans with a sampled parent and using ParentBased(DelegateSampler()) Sampler, inherit the parent span's sampling status
"SampledParentSpanWithParentNeverSample": {sampler: ParentBased(NeverSample()), expect: 1, parent: true, sampledParent: true},
"UnsampledParentSpanWithParentNeverSampler": {sampler: ParentBased(NeverSample()), expect: 0, parent: true, sampledParent: false},
"SampledParentSpanWithParentAlwaysSampler": {sampler: ParentBased(AlwaysSample()), expect: 1, parent: true, sampledParent: true},
"UnsampledParentSpanWithParentAlwaysSampler": {sampler: ParentBased(AlwaysSample()), expect: 0, parent: true, sampledParent: false},
"SampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: 1, parent: true, sampledParent: true},
"UnsampledParentSpanWithParentTraceIdRatioBased_.50": {sampler: ParentBased(TraceIDRatioBased(0.50)), expect: 0, parent: true, sampledParent: false},
} {
tc := tc
t.Run(name, func(t *testing.T) {
Expand Down