ParentOrElse Sampler (#852)

* Added ParentOrElseActivitySampler. Updated AlwaysOn, AlwaysOff, & Probability ActivitySamplers for spec changes.

* Updated documentation comments.

* Added missing braces in ParentOrElse description.

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
Co-authored-by: Eddy Nakamura <ednakamu@microsoft.com>
This commit is contained in:
Mikel Blanchard 2020-07-21 14:46:41 -07:00 committed by GitHub
parent df2d44be07
commit 4c77bc94b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 199 additions and 132 deletions

View File

@ -22,7 +22,7 @@ namespace OpenTelemetry.Trace.Samplers
public sealed class AlwaysOffActivitySampler : ActivitySampler
{
/// <inheritdoc />
public override string Description { get; } = nameof(AlwaysOffActivitySampler);
public override string Description { get; } = "AlwaysOffSampler";
/// <inheritdoc />
public override SamplingResult ShouldSample(in ActivitySamplingParameters samplingParameters)

View File

@ -23,7 +23,7 @@ namespace OpenTelemetry.Trace.Samplers
public sealed class AlwaysOnActivitySampler : ActivitySampler
{
/// <inheritdoc />
public override string Description { get; } = nameof(AlwaysOnActivitySampler);
public override string Description { get; } = "AlwaysOnSampler";
/// <inheritdoc />
public override SamplingResult ShouldSample(in ActivitySamplingParameters samplingParameters)

View File

@ -0,0 +1,75 @@
// <copyright file="ParentOrElseActivitySampler.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Diagnostics;
namespace OpenTelemetry.Trace.Samplers
{
/// <summary>
/// Sampler implementation which will take a sample if parent Activity or any linked Activity is sampled.
/// Otherwise, samples root traces according to the specified delegate sampler.
/// </summary>
public sealed class ParentOrElseActivitySampler : ActivitySampler
{
private readonly ActivitySampler delegateSampler;
/// <summary>
/// Initializes a new instance of the <see cref="ParentOrElseActivitySampler"/> class.
/// </summary>
/// <param name="delegateSampler">The <see cref="ActivitySampler"/> to be called to decide whether or not to sample a root trace.</param>
public ParentOrElseActivitySampler(ActivitySampler delegateSampler)
{
this.delegateSampler = delegateSampler ?? throw new ArgumentNullException(nameof(delegateSampler));
this.Description = $"ParentOrElse{{{delegateSampler.Description}}}";
}
/// <inheritdoc />
public override string Description { get; }
/// <inheritdoc />
public override SamplingResult ShouldSample(in ActivitySamplingParameters samplingParameters)
{
var parentContext = samplingParameters.ParentContext;
if (parentContext == default)
{
// If no parent, use the delegate to determine sampling.
return this.delegateSampler.ShouldSample(samplingParameters);
}
// If the parent is sampled keep the sampling decision.
if ((parentContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{
return new SamplingResult(true);
}
if (samplingParameters.Links != null)
{
// If any parent link is sampled keep the sampling decision.
foreach (var parentLink in samplingParameters.Links)
{
if ((parentLink.Context.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{
return new SamplingResult(true);
}
}
}
// If parent was not sampled, do not sample.
return new SamplingResult(false);
}
}
}

View File

@ -1,4 +1,4 @@
// <copyright file="ProbabilityActivitySampler.cs" company="OpenTelemetry Authors">
// <copyright file="ProbabilityActivitySampler.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -14,14 +14,12 @@
// limitations under the License.
// </copyright>
using System;
using System.Diagnostics;
using System.Globalization;
namespace OpenTelemetry.Trace.Samplers
{
/// <summary>
/// Sampler implementation which will take a sample if parent Activity or any linked Activity is sampled.
/// Otherwise, samples traces according to the specified probability.
/// Samples traces according to the specified probability.
/// </summary>
public sealed class ProbabilityActivitySampler : ActivitySampler
{
@ -70,25 +68,6 @@ namespace OpenTelemetry.Trace.Samplers
/// <inheritdoc />
public override SamplingResult ShouldSample(in ActivitySamplingParameters samplingParameters)
{
// If the parent is sampled keep the sampling decision.
var parentContext = samplingParameters.ParentContext;
if ((parentContext.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{
return new SamplingResult(true);
}
if (samplingParameters.Links != null)
{
// If any parent link is sampled keep the sampling decision.
foreach (var parentLink in samplingParameters.Links)
{
if ((parentLink.Context.TraceFlags & ActivityTraceFlags.Recorded) != 0)
{
return new SamplingResult(true);
}
}
}
// Always sample if we are within probability range. This is true even for child activities (that
// may have had a different sampling decision made) to allow for different sampling policies,
// and dynamic increases to sampling probabilities for debugging purposes.

View File

@ -55,9 +55,7 @@ namespace OpenTelemetry.Trace.Samplers.Test
[Fact]
public void AlwaysOnSampler_GetDescription()
{
// TODO: The name must be AlwaysOnSampler as per spec.
// We should correct it when we replace span sampler with this.
Assert.Equal("AlwaysOnActivitySampler", new AlwaysOnActivitySampler().Description);
Assert.Equal("AlwaysOnSampler", new AlwaysOnActivitySampler().Description);
}
[Theory]
@ -82,9 +80,7 @@ namespace OpenTelemetry.Trace.Samplers.Test
[Fact]
public void AlwaysOffSampler_GetDescription()
{
// TODO: The name must be AlwaysOffSampler as per spec.
// We should correct it when we replace span sampler with this.
Assert.Equal("AlwaysOffActivitySampler", new AlwaysOffActivitySampler().Description);
Assert.Equal("AlwaysOffSampler", new AlwaysOffActivitySampler().Description);
}
}
}

View File

@ -0,0 +1,117 @@
// <copyright file="ParentOrElseSamplerTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xunit;
namespace OpenTelemetry.Trace.Samplers.Test
{
public class ParentOrElseSamplerTests
{
private readonly ParentOrElseActivitySampler parentOrElseAlwaysOnSampler = new ParentOrElseActivitySampler(new AlwaysOnActivitySampler());
private readonly ParentOrElseActivitySampler parentOrElseAlwaysOffSampler = new ParentOrElseActivitySampler(new AlwaysOffActivitySampler());
[Fact]
public void ParentOrElseSampler_SampledParent()
{
// No parent, use delegate sampler.
Assert.Equal(
new SamplingResult(true),
this.parentOrElseAlwaysOnSampler.ShouldSample(default));
// No parent, use delegate sampler.
Assert.Equal(
new SamplingResult(false),
this.parentOrElseAlwaysOffSampler.ShouldSample(default));
// Not sampled parent, don't sample.
Assert.Equal(
new SamplingResult(false),
this.parentOrElseAlwaysOnSampler.ShouldSample(
new ActivitySamplingParameters(
parentContext: new ActivityContext(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None),
traceId: default,
name: "Span",
kind: ActivityKind.Client)));
// Sampled parent, sample.
Assert.Equal(
new SamplingResult(true),
this.parentOrElseAlwaysOffSampler.ShouldSample(
new ActivitySamplingParameters(
parentContext: new ActivityContext(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.Recorded),
traceId: default,
name: "Span",
kind: ActivityKind.Client)));
}
[Fact]
public void ParentOrElseSampler_SampledParentLink()
{
var notSampledLink = new ActivityLink[]
{
new ActivityLink(
new ActivityContext(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None)),
};
var sampledLink = new ActivityLink[]
{
new ActivityLink(
new ActivityContext(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.Recorded)),
};
var notSampledParent = new ActivityContext(
ActivityTraceId.CreateRandom(),
ActivitySpanId.CreateRandom(),
ActivityTraceFlags.None);
// Not sampled link, don't sample.
Assert.Equal(
new SamplingResult(false),
this.parentOrElseAlwaysOnSampler.ShouldSample(
new ActivitySamplingParameters(
parentContext: notSampledParent,
traceId: default,
name: "Span",
kind: ActivityKind.Client,
links: notSampledLink)));
// Sampled link, sample.
Assert.Equal(
new SamplingResult(true),
this.parentOrElseAlwaysOffSampler.ShouldSample(
new ActivitySamplingParameters(
parentContext: notSampledParent,
traceId: default,
name: "Span",
kind: ActivityKind.Client,
links: sampledLink)));
}
}
}

View File

@ -1,4 +1,4 @@
// <copyright file="ProbabilityActivitySamplerTest.cs" company="OpenTelemetry Authors">
// <copyright file="ProbabilityActivitySamplerTest.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@ -14,7 +14,6 @@
// limitations under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xunit;
@ -23,21 +22,7 @@ namespace OpenTelemetry.Trace.Samplers.Test
public class ProbabilityActivitySamplerTest
{
private const string ActivityDisplayName = "MyActivityName";
private const int NumSampleTries = 1000;
private static readonly ActivityKind ActivityKindServer = ActivityKind.Server;
private readonly ActivityTraceId traceId;
private readonly ActivityContext sampledActivityContext;
private readonly ActivityContext notSampledActivityContext;
private readonly ActivityLink sampledLink;
public ProbabilityActivitySamplerTest()
{
this.traceId = ActivityTraceId.CreateRandom();
var parentSpanId = ActivitySpanId.CreateRandom();
this.sampledActivityContext = new ActivityContext(this.traceId, parentSpanId, ActivityTraceFlags.Recorded);
this.notSampledActivityContext = new ActivityContext(this.traceId, parentSpanId, ActivityTraceFlags.None);
this.sampledLink = new ActivityLink(this.sampledActivityContext);
}
[Fact]
public void ProbabilitySampler_OutOfRangeHighProbability()
@ -51,66 +36,6 @@ namespace OpenTelemetry.Trace.Samplers.Test
Assert.Throws<ArgumentOutOfRangeException>(() => new ProbabilityActivitySampler(-0.00001));
}
[Fact]
public void ProbabilitySampler_DifferentProbabilities_NotSampledParent()
{
var neverSample = new ProbabilityActivitySampler(0.0);
AssertSamplerSamplesWithProbability(
neverSample, this.notSampledActivityContext, null, 0.0);
var alwaysSample = new ProbabilityActivitySampler(1.0);
AssertSamplerSamplesWithProbability(
alwaysSample, this.notSampledActivityContext, null, 1.0);
var fiftyPercentSample = new ProbabilityActivitySampler(0.5);
AssertSamplerSamplesWithProbability(
fiftyPercentSample, this.notSampledActivityContext, null, 0.5);
var twentyPercentSample = new ProbabilityActivitySampler(0.2);
AssertSamplerSamplesWithProbability(
twentyPercentSample, this.notSampledActivityContext, null, 0.2);
var twoThirdsSample = new ProbabilityActivitySampler(2.0 / 3.0);
AssertSamplerSamplesWithProbability(
twoThirdsSample, this.notSampledActivityContext, null, 2.0 / 3.0);
}
[Fact]
public void ProbabilitySampler_DifferentProbabilities_SampledParent()
{
var neverSample = new ProbabilityActivitySampler(0.0);
AssertSamplerSamplesWithProbability(
neverSample, this.sampledActivityContext, null, 1.0);
var alwaysSample = new ProbabilityActivitySampler(1.0);
AssertSamplerSamplesWithProbability(
alwaysSample, this.sampledActivityContext, null, 1.0);
var fiftyPercentSample = new ProbabilityActivitySampler(0.5);
AssertSamplerSamplesWithProbability(
fiftyPercentSample, this.sampledActivityContext, null, 1.0);
var twentyPercentSample = new ProbabilityActivitySampler(0.2);
AssertSamplerSamplesWithProbability(
twentyPercentSample, this.sampledActivityContext, null, 1.0);
var twoThirdsSample = new ProbabilityActivitySampler(2.0 / 3.0);
AssertSamplerSamplesWithProbability(
twoThirdsSample, this.sampledActivityContext, null, 1.0);
}
[Fact]
public void ProbabilitySampler_DifferentProbabilities_SampledParentLink()
{
var neverSample = new ProbabilityActivitySampler(0.0);
AssertSamplerSamplesWithProbability(
neverSample, this.notSampledActivityContext, new List<ActivityLink>() { this.sampledLink }, 1.0);
var alwaysSample = new ProbabilityActivitySampler(1.0);
AssertSamplerSamplesWithProbability(
alwaysSample, this.notSampledActivityContext, new List<ActivityLink>() { this.sampledLink }, 1.0);
var fiftyPercentSample = new ProbabilityActivitySampler(0.5);
AssertSamplerSamplesWithProbability(
fiftyPercentSample, this.notSampledActivityContext, new List<ActivityLink>() { this.sampledLink }, 1.0);
var twentyPercentSample = new ProbabilityActivitySampler(0.2);
AssertSamplerSamplesWithProbability(
twentyPercentSample, this.notSampledActivityContext, new List<ActivityLink>() { this.sampledLink }, 1.0);
var twoThirdsSample = new ProbabilityActivitySampler(2.0 / 3.0);
AssertSamplerSamplesWithProbability(
twoThirdsSample, this.notSampledActivityContext, new List<ActivityLink>() { this.sampledLink }, 1.0);
}
[Fact]
public void ProbabilitySampler_SampleBasedOnTraceId()
{
@ -187,30 +112,5 @@ namespace OpenTelemetry.Trace.Samplers.Test
var expectedDescription = "ProbabilityActivitySampler{0.500000}";
Assert.Equal(expectedDescription, new ProbabilityActivitySampler(0.5).Description);
}
// Applies the given sampler to NumSampleTries random traceId/spanId pairs.
private static void AssertSamplerSamplesWithProbability(
ActivitySampler sampler, ActivityContext parent, List<ActivityLink> links, double probability)
{
var count = 0; // Count of spans with sampling enabled
for (var i = 0; i < NumSampleTries; i++)
{
if (sampler.ShouldSample(new ActivitySamplingParameters(
parent,
ActivityTraceId.CreateRandom(),
ActivityDisplayName,
ActivityKindServer,
null,
links)).IsSampled)
{
count++;
}
}
var proportionSampled = (double)count / NumSampleTries;
// Allow for a large amount of slop (+/- 10%) in number of sampled traces, to avoid flakiness.
Assert.True(proportionSampled < probability + 0.1 && proportionSampled > probability - 0.1);
}
}
}