From 577ae6cce35dde254af4ddcd065903aacd158afa Mon Sep 17 00:00:00 2001 From: Cijo Thomas Date: Wed, 20 May 2020 22:47:07 -0700 Subject: [PATCH] Added sampler to Activity - v1 (#683) * Added sampler to Activity --- .../Exporters/Console/TestConsoleActivity.cs | 6 ++ src/OpenTelemetry/Trace/ActivitySampler.cs | 48 ++++++++++ .../Configuration/OpenTelemetryBuilder.cs | 13 +++ .../Trace/Configuration/OpenTelemetrySdk.cs | 38 +++++++- .../Samplers/AlwaysOffActivitySampler.cs | 35 +++++++ .../Trace/Samplers/AlwaysOnActivitySampler.cs | 36 ++++++++ .../Trace/Samplers/ActivitySamplersTest.cs | 92 +++++++++++++++++++ 7 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 src/OpenTelemetry/Trace/ActivitySampler.cs create mode 100644 src/OpenTelemetry/Trace/Samplers/AlwaysOffActivitySampler.cs create mode 100644 src/OpenTelemetry/Trace/Samplers/AlwaysOnActivitySampler.cs create mode 100644 test/OpenTelemetry.Tests/Implementation/Trace/Samplers/ActivitySamplersTest.cs diff --git a/samples/Exporters/Console/TestConsoleActivity.cs b/samples/Exporters/Console/TestConsoleActivity.cs index 619498a9d8f..3702abbd4c1 100644 --- a/samples/Exporters/Console/TestConsoleActivity.cs +++ b/samples/Exporters/Console/TestConsoleActivity.cs @@ -49,6 +49,12 @@ internal static object Run(ConsoleActivityOptions options) if (parent != null) { parent.DisplayName = "HttpIn DisplayName"; + + // IsAllDataRequested is equivalent of Span.IsRecording + if (parent.IsAllDataRequested) + { + parent.AddTag("expensive data", "This data is expensive to obtain. Avoid it if activity is not being recorded"); + } } try diff --git a/src/OpenTelemetry/Trace/ActivitySampler.cs b/src/OpenTelemetry/Trace/ActivitySampler.cs new file mode 100644 index 00000000000..3f666fb6db7 --- /dev/null +++ b/src/OpenTelemetry/Trace/ActivitySampler.cs @@ -0,0 +1,48 @@ +// +// 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. +// +using System.Collections.Generic; +using System.Diagnostics; + +namespace OpenTelemetry.Trace +{ + /// + /// Sampler to select data to be exported. This sampler executes before Activity object is created. + /// + public abstract class ActivitySampler + { + /// + /// Gets the sampler description. + /// + public abstract string Description { get; } + + /// + /// Checks whether activity needs to be created and tracked. + /// + /// Parent activity context. Typically taken from the wire. + /// Trace ID of a activity to be created. + /// Span ID of a activity to be created. + /// Name (DisplayName) of the activity to be created. Note, that the name of the activity is settable. + /// So this name can be changed later and Sampler implementation should assume that. + /// Typical example of a name change is when representing incoming http request + /// has a name of url path and then being updated with route name when routing complete. + /// + /// The kind of the Activity. + /// Initial set of Tags for the Activity being constructed. + /// Links associated with the activity. + /// Sampling decision on whether activity needs to be sampled or not. + public abstract SamplingResult ShouldSample(in ActivityContext parentContext, in ActivityTraceId traceId, in ActivitySpanId spanId, string name, ActivityKind activityKind, IEnumerable> tags, IEnumerable links); + } +} diff --git a/src/OpenTelemetry/Trace/Configuration/OpenTelemetryBuilder.cs b/src/OpenTelemetry/Trace/Configuration/OpenTelemetryBuilder.cs index b11005394a2..ff672505304 100644 --- a/src/OpenTelemetry/Trace/Configuration/OpenTelemetryBuilder.cs +++ b/src/OpenTelemetry/Trace/Configuration/OpenTelemetryBuilder.cs @@ -32,6 +32,8 @@ internal OpenTelemetryBuilder() internal ActivityProcessorPipelineBuilder ProcessingPipeline { get; private set; } + internal ActivitySampler Sampler { get; private set; } + internal HashSet ActivitySourceNames { get; private set; } /// @@ -52,6 +54,17 @@ public OpenTelemetryBuilder SetProcessorPipeline(Action + /// Configures sampler. + /// + /// Sampler instance. + /// Returns for chaining. + public OpenTelemetryBuilder SetSampler(ActivitySampler sampler) + { + this.Sampler = sampler ?? throw new ArgumentNullException(nameof(sampler)); + return this; + } + /// /// Adds given activitysource name to the list of subscribed sources. /// diff --git a/src/OpenTelemetry/Trace/Configuration/OpenTelemetrySdk.cs b/src/OpenTelemetry/Trace/Configuration/OpenTelemetrySdk.cs index 5da080c9921..ee1a9ad3d20 100644 --- a/src/OpenTelemetry/Trace/Configuration/OpenTelemetrySdk.cs +++ b/src/OpenTelemetry/Trace/Configuration/OpenTelemetrySdk.cs @@ -17,6 +17,7 @@ using System; using System.Diagnostics; using OpenTelemetry.Trace.Export; +using OpenTelemetry.Trace.Samplers; namespace OpenTelemetry.Trace.Configuration { @@ -40,6 +41,8 @@ public static void EnableOpenTelemetry(Action configureOpe var openTelemetryBuilder = new OpenTelemetryBuilder(); configureOpenTelemetryBuilder(openTelemetryBuilder); + ActivitySampler sampler = openTelemetryBuilder.Sampler ?? new AlwaysOnActivitySampler(); + ActivityProcessor activityProcessor; if (openTelemetryBuilder.ProcessingPipeline == null) { @@ -65,9 +68,40 @@ public static void EnableOpenTelemetry(Action configureOpe // or not ShouldListenTo = (activitySource) => openTelemetryBuilder.ActivitySourceNames.Contains(activitySource.Name.ToUpperInvariant()), - // The following parameters are not used now. + // The following parameter is not used now. GetRequestedDataUsingParentId = (ref ActivityCreationOptions options) => ActivityDataRequest.AllData, - GetRequestedDataUsingContext = (ref ActivityCreationOptions options) => ActivityDataRequest.AllData, + + // This delegate informs ActivitySource about sampling decision. + // Following simple behavior is enabled now: + // If Sampler returns IsSampled as true, returns ActivityDataRequest.AllDataAndRecorded + // This creates Activity and sets its IsAllDataRequested to true. + // Library authors can check activity.IsAllDataRequested and avoid + // doing any additional telemetry population. + // Activity.IsAllDataRequested is the equivalent of Span.IsRecording + // + // If Sampler returns IsSampled as false, returns ActivityDataRequest.None + // This prevents Activity from being created at all. + GetRequestedDataUsingContext = (ref ActivityCreationOptions options) => + { + var shouldSample = sampler.ShouldSample( + options.Parent, + options.Parent.TraceId, + default(ActivitySpanId), // Passing default SpanId here. The actual SpanId is not known before actual Activity creation + options.Name, + options.Kind, + options.Tags, + options.Links); + if (shouldSample.IsSampled) + { + return ActivityDataRequest.AllDataAndRecorded; + } + else + { + return ActivityDataRequest.None; + } + + // TODO: Improve this to properly use ActivityDataRequest.AllData, PropagationData as well. + }, }; ActivitySource.AddActivityListener(listener); diff --git a/src/OpenTelemetry/Trace/Samplers/AlwaysOffActivitySampler.cs b/src/OpenTelemetry/Trace/Samplers/AlwaysOffActivitySampler.cs new file mode 100644 index 00000000000..fe2ee3d32ce --- /dev/null +++ b/src/OpenTelemetry/Trace/Samplers/AlwaysOffActivitySampler.cs @@ -0,0 +1,35 @@ +// +// 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. +// +using System.Collections.Generic; +using System.Diagnostics; + +namespace OpenTelemetry.Trace.Samplers +{ + /// + /// Sampler implementation which never samples any activity. + /// + public sealed class AlwaysOffActivitySampler : ActivitySampler + { + /// + public override string Description { get; } = nameof(AlwaysOffActivitySampler); + + /// + public override SamplingResult ShouldSample(in ActivityContext parentContext, in ActivityTraceId traceId, in ActivitySpanId spanId, string name, ActivityKind activityKind, IEnumerable> tags, IEnumerable links) + { + return new SamplingResult(false); + } + } +} diff --git a/src/OpenTelemetry/Trace/Samplers/AlwaysOnActivitySampler.cs b/src/OpenTelemetry/Trace/Samplers/AlwaysOnActivitySampler.cs new file mode 100644 index 00000000000..0130c21921d --- /dev/null +++ b/src/OpenTelemetry/Trace/Samplers/AlwaysOnActivitySampler.cs @@ -0,0 +1,36 @@ +// +// 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. +// +using System.Collections.Generic; +using System.Diagnostics; + +namespace OpenTelemetry.Trace.Samplers +{ + /// + /// Sampler implementation which samples every activity. + /// This sampler will be used as the default Sampler, if no other Sampler is configured. + /// + public sealed class AlwaysOnActivitySampler : ActivitySampler + { + /// + public override string Description { get; } = nameof(AlwaysOnActivitySampler); + + /// + public override SamplingResult ShouldSample(in ActivityContext parentContext, in ActivityTraceId traceId, in ActivitySpanId spanId, string name, ActivityKind activityKind, IEnumerable> tags, IEnumerable links) + { + return new SamplingResult(true); + } + } +} diff --git a/test/OpenTelemetry.Tests/Implementation/Trace/Samplers/ActivitySamplersTest.cs b/test/OpenTelemetry.Tests/Implementation/Trace/Samplers/ActivitySamplersTest.cs new file mode 100644 index 00000000000..76ac02f8e08 --- /dev/null +++ b/test/OpenTelemetry.Tests/Implementation/Trace/Samplers/ActivitySamplersTest.cs @@ -0,0 +1,92 @@ +// +// 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. +// +using System.Collections.Generic; +using System.Diagnostics; +using Xunit; + +namespace OpenTelemetry.Trace.Samplers.Test +{ + public class ActivitySamplersTest + { + private static readonly ActivityKind ActivityKindServer = ActivityKind.Server; + private readonly ActivityTraceId traceId; + private readonly ActivitySpanId spanId; + private readonly ActivitySpanId parentSpanId; + + public ActivitySamplersTest() + { + traceId = ActivityTraceId.CreateRandom(); + spanId = ActivitySpanId.CreateRandom(); + parentSpanId = ActivitySpanId.CreateRandom(); + } + + [Theory] + [InlineData(ActivityTraceFlags.Recorded)] + [InlineData(ActivityTraceFlags.None)] + public void AlwaysOnSampler_AlwaysReturnTrue(ActivityTraceFlags flags) + { + var parentContext = new ActivityContext(traceId, parentSpanId, flags); + var link = new ActivityLink(parentContext); + + Assert.True( + new AlwaysOnActivitySampler() + .ShouldSample( + parentContext, + traceId, + spanId, + "Another name", + ActivityKindServer, + null, + new List() { link }).IsSampled); + } + + [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); + } + + [Theory] + [InlineData(ActivityTraceFlags.Recorded)] + [InlineData(ActivityTraceFlags.None)] + public void AlwaysOffSampler_AlwaysReturnFalse(ActivityTraceFlags flags) + { + var parentContext = new ActivityContext(traceId, parentSpanId, flags); + var link = new ActivityLink(parentContext); + + Assert.False( + new AlwaysOffActivitySampler() + .ShouldSample( + parentContext, + traceId, + spanId, + "Another name", + ActivityKindServer, + null, + new List() { link }).IsSampled); + } + + [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); + } + } +}